Compare commits

..

1 Commits

Author SHA1 Message Date
akwizgran
580be7cfdc Log DB table names. 2022-06-08 15:10:11 +01:00
118 changed files with 997 additions and 4737 deletions

View File

@@ -15,8 +15,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 10409
versionName "1.4.9"
versionCode 10408
versionName "1.4.8"
consumerProguardFiles 'proguard-rules.txt'
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@@ -7,7 +7,6 @@ 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.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
@NotNullByDefault
public interface ConnectionManager {
@@ -46,14 +45,6 @@ public interface ConnectionManager {
void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w);
/**
* Manages an outgoing connection to a contact via a mailbox. The IDs of
* any messages sent or acked are added to the given
* {@link OutgoingSessionRecord}.
*/
void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord);
/**
* Manages an outgoing connection to a contact over a duplex transport.
*/

View File

@@ -126,11 +126,16 @@ public interface DatabaseComponent extends TransactionManager {
TransportKeys k) throws DbException;
/**
* Returns true if there are any acks to send to the given contact.
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAcksToSend(Transaction txn, ContactId c) throws DbException;
boolean containsAnythingToSend(Transaction txn, ContactId c,
long maxLatency, boolean eager) throws DbException;
/**
* Returns true if the database contains the given contact for the given
@@ -156,18 +161,6 @@ public interface DatabaseComponent extends TransactionManager {
*/
boolean containsIdentity(Transaction txn, AuthorId a) throws DbException;
/**
* Returns true if there are any messages to send to the given contact
* over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsMessagesToSend(Transaction txn, ContactId c,
long maxLatency, boolean eager) throws DbException;
/**
* Returns true if the database contains the given pending contact.
* <p/>
@@ -541,18 +534,15 @@ public interface DatabaseComponent extends TransactionManager {
*/
long getNextCleanupDeadline(Transaction txn) throws DbException;
/**
/*
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact over a transport with
* the given latency.
* <p>
* The returned value may be zero if a message is due to be sent
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
* message is due to be sent to the given contact. The returned value may
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE if
* no messages are scheduled to be sent.
* <p/>
* Read-only.
*/
long getNextSendTime(Transaction txn, ContactId c, long maxLatency)
throws DbException;
long getNextSendTime(Transaction txn, ContactId c) throws DbException;
/**
* Returns the pending contact with the given ID.

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.plugin.TransportId;
import java.util.List;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.HOURS;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_FRAME_LENGTH;
import static org.briarproject.bramble.api.transport.TransportConstants.MAX_PAYLOAD_LENGTH;
@@ -66,8 +65,4 @@ public interface MailboxConstants {
*/
long PROBLEM_MS_SINCE_LAST_SUCCESS = HOURS.toMillis(1);
/**
* The maximum latency of the mailbox transport in milliseconds.
*/
long MAX_LATENCY = DAYS.toMillis(14);
}

View File

@@ -35,9 +35,6 @@ public interface MailboxSettingsManager {
void recordSuccessfulConnection(Transaction txn, long now)
throws DbException;
void recordSuccessfulConnection(Transaction txn, long now,
List<MailboxVersion> versions) throws DbException;
void recordFailedConnectionAttempt(Transaction txn, long now)
throws DbException;

View File

@@ -63,26 +63,9 @@ public interface MailboxUpdateManager {
*/
String GROUP_KEY_SENT_CLIENT_SUPPORTS = "sentClientSupports";
/**
* Returns the latest {@link MailboxUpdate} sent to the given contact.
* <p>
* If we have our own mailbox then the update will be a
* {@link MailboxUpdateWithMailbox} containing the
* {@link MailboxProperties} the contact should use for communicating with
* our mailbox.
*/
MailboxUpdate getLocalUpdate(Transaction txn, ContactId c)
throws DbException;
/**
* Returns the latest {@link MailboxUpdate} received from the given
* contact, or null if no update has been received.
* <p>
* If the contact has a mailbox then the update will be a
* {@link MailboxUpdateWithMailbox} containing the
* {@link MailboxProperties} we should use for communicating with the
* contact's mailbox.
*/
@Nullable
MailboxUpdate getRemoteUpdate(Transaction txn, ContactId c)
throws DbException;

View File

@@ -0,0 +1,15 @@
package org.briarproject.bramble.api.sync;
import java.util.Collection;
/**
* An interface for holding the IDs of messages sent and acked during an
* outgoing {@link SyncSession} so they can be recorded in the DB as sent
* or acked at some later time.
*/
public interface DeferredSendHandler {
void onAckSent(Collection<MessageId> acked);
void onMessageSent(MessageId sent);
}

View File

@@ -1,37 +0,0 @@
package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.concurrent.ThreadSafe;
/**
* A container for holding the IDs of messages sent and acked during an
* outgoing {@link SyncSession}, so they can be recorded in the DB as sent
* or acked at some later time.
*/
@ThreadSafe
@NotNullByDefault
public class OutgoingSessionRecord {
private final Collection<MessageId> ackedIds = new CopyOnWriteArrayList<>();
private final Collection<MessageId> sentIds = new CopyOnWriteArrayList<>();
public void onAckSent(Collection<MessageId> acked) {
ackedIds.addAll(acked);
}
public void onMessageSent(MessageId sent) {
sentIds.add(sent);
}
public Collection<MessageId> getAckedIds() {
return ackedIds;
}
public Collection<MessageId> getSentIds() {
return sentIds;
}
}

View File

@@ -12,30 +12,12 @@ import javax.annotation.Nullable;
@NotNullByDefault
public interface SyncSessionFactory {
/**
* Creates a session for receiving data from a contact.
*/
SyncSession createIncomingSession(ContactId c, InputStream in,
PriorityHandler handler);
/**
* Creates a session for sending data to a contact over a simplex transport.
*
* @param eager True if messages should be sent eagerly, ie regardless of
* whether they're due for retransmission.
*/
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, boolean eager, StreamWriter streamWriter);
/**
* Creates a session for sending data to a contact via a mailbox. The IDs
* of any messages sent or acked will be added to the given
* {@link OutgoingSessionRecord}.
*/
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, StreamWriter streamWriter,
OutgoingSessionRecord sessionRecord);
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, int maxIdleTime, StreamWriter streamWriter,
@Nullable Priority priority);

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.Group.Visibility;
import java.util.Collection;
@@ -16,19 +15,12 @@ import javax.annotation.concurrent.Immutable;
@NotNullByDefault
public class GroupVisibilityUpdatedEvent extends Event {
private final Visibility visibility;
private final Collection<ContactId> affected;
public GroupVisibilityUpdatedEvent(Visibility visibility,
Collection<ContactId> affected) {
this.visibility = visibility;
public GroupVisibilityUpdatedEvent(Collection<ContactId> affected) {
this.affected = affected;
}
public Visibility getVisibility() {
return visibility;
}
/**
* Returns the contacts affected by the update.
*/

View File

@@ -40,7 +40,7 @@ public class IoUtils {
}
}
public static void delete(File f) {
private static void delete(File f) {
if (!f.delete() && LOG.isLoggable(WARNING))
LOG.warning("Could not delete " + f.getAbsolutePath());
}

View File

@@ -13,7 +13,6 @@ import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.api.transport.StreamReaderFactory;
@@ -101,16 +100,7 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) {
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, c, t, w, null));
}
@Override
public void manageOutgoingConnection(ContactId c, TransportId t,
TransportConnectionWriter w, OutgoingSessionRecord sessionRecord) {
ioExecutor.execute(new OutgoingSimplexSyncConnection(keyManager,
connectionRegistry, streamReaderFactory, streamWriterFactory,
syncSessionFactory, transportPropertyManager, c, t, w,
sessionRecord));
syncSessionFactory, transportPropertyManager, c, t, w));
}
@Override

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.properties.TransportPropertyManager;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.SyncSessionFactory;
import org.briarproject.bramble.api.transport.KeyManager;
@@ -17,8 +16,6 @@ import org.briarproject.bramble.api.transport.StreamWriterFactory;
import java.io.IOException;
import javax.annotation.Nullable;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@@ -29,8 +26,6 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
private final ContactId contactId;
private final TransportId transportId;
private final TransportConnectionWriter writer;
@Nullable
private final OutgoingSessionRecord sessionRecord;
OutgoingSimplexSyncConnection(KeyManager keyManager,
ConnectionRegistry connectionRegistry,
@@ -39,15 +34,13 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
SyncSessionFactory syncSessionFactory,
TransportPropertyManager transportPropertyManager,
ContactId contactId, TransportId transportId,
TransportConnectionWriter writer,
@Nullable OutgoingSessionRecord sessionRecord) {
TransportConnectionWriter writer) {
super(keyManager, connectionRegistry, streamReaderFactory,
streamWriterFactory, syncSessionFactory,
transportPropertyManager);
this.contactId = contactId;
this.transportId = transportId;
this.writer = writer;
this.sessionRecord = sessionRecord;
}
@Override
@@ -78,16 +71,10 @@ class OutgoingSimplexSyncConnection extends SyncConnection implements Runnable {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
ContactId c = requireNonNull(ctx.getContactId());
if (sessionRecord == null) {
// Use eager retransmission if the transport is lossy and cheap
return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(),
w.isLossyAndCheap(), streamWriter);
} else {
return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), streamWriter,
sessionRecord);
}
// Use eager retransmission if the transport is lossy and cheap
return syncSessionFactory.createSimplexOutgoingSession(c,
ctx.getTransportId(), w.getMaxLatency(), w.isLossyAndCheap(),
streamWriter);
}
}

View File

@@ -163,11 +163,16 @@ interface Database<T> {
throws DbException;
/**
* Returns true if there are any acks to send to the given contact.
* Returns true if there are any acks or messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsAcksToSend(T txn, ContactId c) throws DbException;
boolean containsAnythingToSend(T txn, ContactId c, long maxLatency,
boolean eager) throws DbException;
/**
* Returns true if the database contains the given contact for the given
@@ -207,18 +212,6 @@ interface Database<T> {
*/
boolean containsMessage(T txn, MessageId m) throws DbException;
/**
* Returns true if there are any messages to send to the given
* contact over a transport with the given maximum latency.
* <p/>
* Read-only.
*
* @param eager True if messages that are not yet due for retransmission
* should be included
*/
boolean containsMessagesToSend(T txn, ContactId c, long maxLatency,
boolean eager) throws DbException;
/**
* Returns true if the database contains the given pending contact.
* <p/>
@@ -587,16 +580,13 @@ interface Database<T> {
/**
* Returns the next time (in milliseconds since the Unix epoch) when a
* message is due to be sent to the given contact over a transport with
* the given latency.
* <p>
* The returned value may be zero if a message is due to be sent
* immediately, or Long.MAX_VALUE if no messages are scheduled to be sent.
* message is due to be sent to the given contact. The returned value may
* be zero if a message is due to be sent immediately, or Long.MAX_VALUE
* if no messages are scheduled to be sent.
* <p/>
* Read-only.
*/
long getNextSendTime(T txn, ContactId c, long maxLatency)
throws DbException;
long getNextSendTime(T txn, ContactId c) throws DbException;
/**
* Returns the pending contact with the given ID.

View File

@@ -342,12 +342,12 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public boolean containsAcksToSend(Transaction transaction, ContactId c)
throws DbException {
public boolean containsAnythingToSend(Transaction transaction, ContactId c,
long maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.containsAcksToSend(txn, c);
return db.containsAnythingToSend(txn, c, maxLatency, eager);
}
@Override
@@ -373,15 +373,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
return db.containsIdentity(txn, a);
}
@Override
public boolean containsMessagesToSend(Transaction transaction, ContactId c,
long maxLatency, boolean eager) throws DbException {
T txn = unbox(transaction);
if (!db.containsContact(txn, c))
throw new NoSuchContactException();
return db.containsMessagesToSend(txn, c, maxLatency, eager);
}
@Override
public boolean containsPendingContact(Transaction transaction,
PendingContactId p) throws DbException {
@@ -814,10 +805,10 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
@Override
public long getNextSendTime(Transaction transaction, ContactId c,
long maxLatency) throws DbException {
public long getNextSendTime(Transaction transaction, ContactId c)
throws DbException {
T txn = unbox(transaction);
return db.getNextSendTime(txn, c, maxLatency);
return db.getNextSendTime(txn, c);
}
@Override
@@ -1025,8 +1016,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
db.getGroupVisibility(txn, id).keySet();
db.removeGroup(txn, id);
transaction.attach(new GroupRemovedEvent(g));
transaction.attach(new GroupVisibilityUpdatedEvent(INVISIBLE,
affected));
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
}
@Override
@@ -1151,7 +1141,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
else db.setGroupVisibility(txn, c, g, v == SHARED);
List<ContactId> affected = singletonList(c);
transaction.attach(new GroupVisibilityUpdatedEvent(v, affected));
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
}
@Override

View File

@@ -3,15 +3,16 @@ package org.briarproject.bramble.db;
class DatabaseTypes {
private final String hashType, secretType, binaryType;
private final String counterType, stringType;
private final String counterType, stringType, schemaQuery;
public DatabaseTypes(String hashType, String secretType, String binaryType,
String counterType, String stringType) {
DatabaseTypes(String hashType, String secretType, String binaryType,
String counterType, String stringType, String schemaQuery) {
this.hashType = hashType;
this.secretType = secretType;
this.binaryType = binaryType;
this.counterType = counterType;
this.stringType = stringType;
this.schemaQuery = schemaQuery;
}
/**
@@ -31,4 +32,8 @@ class DatabaseTypes {
s = s.replaceAll("_STRING", stringType);
return s;
}
String getSchemaQuery() {
return schemaQuery;
}
}

View File

@@ -41,8 +41,9 @@ class H2Database extends JdbcDatabase {
private static final String BINARY_TYPE = "BINARY";
private static final String COUNTER_TYPE = "INT NOT NULL AUTO_INCREMENT";
private static final String STRING_TYPE = "VARCHAR";
private static final String SCHEMA_QUERY = "SHOW TABLES";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE);
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE, SCHEMA_QUERY);
private final DatabaseConfig config;
private final String url;

View File

@@ -41,8 +41,11 @@ class HyperSqlDatabase extends JdbcDatabase {
private static final String COUNTER_TYPE =
"INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY(START WITH 1)";
private static final String STRING_TYPE = "VARCHAR";
private static final String SCHEMA_QUERY =
"SELECT TABLE_NAME FROM INFORMATION_SCHEMA.SYSTEM_TABLES"
+ " WHERE TABLE_TYPE='TABLE'";
private static final DatabaseTypes dbTypes = new DatabaseTypes(HASH_TYPE,
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE);
SECRET_TYPE, BINARY_TYPE, COUNTER_TYPE, STRING_TYPE, SCHEMA_QUERY);
private final DatabaseConfig config;
private final String url;

View File

@@ -405,6 +405,7 @@ abstract class JdbcDatabase implements Database<Connection> {
boolean compact;
Connection txn = startTransaction();
try {
if (LOG.isLoggable(INFO)) logSchema(txn);
if (reopen) {
Settings s = getSettings(txn, DB_SETTINGS_NAMESPACE);
wasDirtyOnInitialisation = isDirty(s);
@@ -447,6 +448,24 @@ abstract class JdbcDatabase implements Database<Connection> {
return wasDirtyOnInitialisation;
}
private void logSchema(Connection txn) throws DbException {
Statement s = null;
ResultSet rs = null;
try {
s = txn.createStatement();
rs = s.executeQuery(dbTypes.getSchemaQuery());
StringBuilder sb = new StringBuilder("Tables:");
while (rs.next()) sb.append('\n').append(rs.getString(1));
LOG.info(sb.toString());
rs.close();
s.close();
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(s, LOG, WARNING);
throw new DbException(e);
}
}
/**
* Compares the schema version stored in the database with the schema
* version used by the current code and applies any suitable migrations to
@@ -1147,8 +1166,8 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public boolean containsAcksToSend(Connection txn, ContactId c)
throws DbException {
public boolean containsAnythingToSend(Connection txn, ContactId c,
long maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
@@ -1160,7 +1179,34 @@ abstract class JdbcDatabase implements Database<Connection> {
boolean acksToSend = rs.next();
rs.close();
ps.close();
return acksToSend;
if (acksToSend) return true;
if (eager) {
sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, maxLatency);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
@@ -1280,46 +1326,6 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
@Override
public boolean containsMessagesToSend(Connection txn, ContactId c,
long maxLatency, boolean eager) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
if (eager) {
String sql = "SELECT NULL from statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
} else {
long now = clock.currentTimeMillis();
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (expiry <= ? OR maxLatency IS NULL"
+ " OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, now);
ps.setLong(4, maxLatency);
}
rs = ps.executeQuery();
boolean messagesToSend = rs.next();
rs.close();
ps.close();
return messagesToSend;
} catch (SQLException e) {
tryToClose(rs, LOG, WARNING);
tryToClose(ps, LOG, WARNING);
throw new DbException(e);
}
}
@Override
public boolean containsPendingContact(Connection txn, PendingContactId p)
throws DbException {
@@ -2490,28 +2496,12 @@ abstract class JdbcDatabase implements Database<Connection> {
}
@Override
public long getNextSendTime(Connection txn, ContactId c, long maxLatency)
public long getNextSendTime(Connection txn, ContactId c)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Are any messages sendable immediately?
String sql = "SELECT NULL FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"
+ " AND (maxLatency IS NULL OR ? < maxLatency)";
ps = txn.prepareStatement(sql);
ps.setInt(1, c.getInt());
ps.setInt(2, DELIVERED.getValue());
ps.setLong(3, maxLatency);
rs = ps.executeQuery();
boolean found = rs.next();
rs.close();
ps.close();
if (found) return 0;
// When is the earliest expiry time (could be in the past)?
sql = "SELECT expiry FROM statuses"
String sql = "SELECT expiry FROM statuses"
+ " WHERE contactId = ? AND state = ?"
+ " AND groupShared = TRUE AND messageShared = TRUE"
+ " AND deleted = FALSE AND seen = FALSE"

View File

@@ -22,21 +22,10 @@ interface ConnectivityChecker {
* the check succeeds. If a check is already running then the observer is
* called when the check succeeds. If a connectivity check has recently
* succeeded then the observer is called immediately.
* <p>
* Observers are removed after being called, or when the checker is
* {@link #destroy() destroyed}.
*/
void checkConnectivity(MailboxProperties properties,
ConnectivityObserver o);
/**
* Removes an observer that was added via
* {@link #checkConnectivity(MailboxProperties, ConnectivityObserver)}. If
* there are no remaining observers and a connectivity check is running
* then the check will be cancelled.
*/
void removeObserver(ConnectivityObserver o);
interface ConnectivityObserver {
void onConnectivityCheckSucceeded();
}

View File

@@ -80,7 +80,8 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
> CONNECTIVITY_CHECK_FRESHNESS_MS) {
// The last connectivity check is stale, start a new one
connectivityObservers.add(o);
ApiCall task = createConnectivityCheckTask(properties);
ApiCall task =
createConnectivityCheckTask(properties);
connectivityCheck = mailboxApiCaller.retryWithBackoff(task);
} else {
// The last connectivity check is fresh
@@ -107,16 +108,4 @@ abstract class ConnectivityCheckerImpl implements ConnectivityChecker {
o.onConnectivityCheckSucceeded();
}
}
@Override
public void removeObserver(ConnectivityObserver o) {
synchronized (lock) {
if (destroyed) return;
connectivityObservers.remove(o);
if (connectivityObservers.isEmpty() && connectivityCheck != null) {
connectivityCheck.cancel();
connectivityCheck = null;
}
}
}
}

View File

@@ -1,122 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.logging.Logger.getLogger;
@ThreadSafe
@NotNullByDefault
class ContactMailboxClient implements MailboxClient {
private static final Logger LOG =
getLogger(ContactMailboxClient.class.getName());
private final MailboxWorkerFactory workerFactory;
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor reachabilityMonitor;
private final Object lock = new Object();
@GuardedBy("lock")
@Nullable
private MailboxWorker uploadWorker = null, downloadWorker = null;
@Inject
ContactMailboxClient(MailboxWorkerFactory workerFactory,
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor) {
this.workerFactory = workerFactory;
this.connectivityChecker = connectivityChecker;
this.reachabilityMonitor = reachabilityMonitor;
}
@Override
public void start() {
LOG.info("Started");
// Nothing to do until contact is assigned
}
@Override
public void destroy() {
LOG.info("Destroyed");
MailboxWorker uploadWorker, downloadWorker;
synchronized (lock) {
uploadWorker = this.uploadWorker;
this.uploadWorker = null;
downloadWorker = this.downloadWorker;
this.downloadWorker = null;
}
if (uploadWorker != null) uploadWorker.destroy();
if (downloadWorker != null) downloadWorker.destroy();
}
@Override
public void assignContactForUpload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for upload");
if (properties.isOwner()) throw new IllegalArgumentException();
// For a contact's mailbox we should always be uploading to the outbox
// assigned to us by the contact
if (!folderId.equals(properties.getOutboxId())) {
throw new IllegalArgumentException();
}
MailboxWorker uploadWorker = workerFactory.createUploadWorker(
connectivityChecker, properties, folderId, contactId);
synchronized (lock) {
if (this.uploadWorker != null) throw new IllegalStateException();
this.uploadWorker = uploadWorker;
}
uploadWorker.start();
}
@Override
public void deassignContactForUpload(ContactId contactId) {
LOG.info("Contact deassigned for upload");
MailboxWorker uploadWorker;
synchronized (lock) {
uploadWorker = this.uploadWorker;
this.uploadWorker = null;
}
if (uploadWorker != null) uploadWorker.destroy();
}
@Override
public void assignContactForDownload(ContactId contactId,
MailboxProperties properties, MailboxFolderId folderId) {
LOG.info("Contact assigned for download");
if (properties.isOwner()) throw new IllegalArgumentException();
// For a contact's mailbox we should always be downloading from the
// inbox assigned to us by the contact
if (!folderId.equals(properties.getInboxId())) {
throw new IllegalArgumentException();
}
MailboxWorker downloadWorker =
workerFactory.createDownloadWorkerForContactMailbox(
connectivityChecker, reachabilityMonitor, properties);
synchronized (lock) {
if (this.downloadWorker != null) throw new IllegalStateException();
this.downloadWorker = downloadWorker;
}
downloadWorker.start();
}
@Override
public void deassignContactForDownload(ContactId contactId) {
LOG.info("Contact deassigned for download");
MailboxWorker downloadWorker;
synchronized (lock) {
downloadWorker = this.downloadWorker;
this.downloadWorker = null;
}
if (downloadWorker != null) downloadWorker.destroy();
}
}

View File

@@ -5,6 +5,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@@ -22,11 +24,16 @@ class ContactMailboxConnectivityChecker extends ConnectivityCheckerImpl {
@Override
ApiCall createConnectivityCheckTask(MailboxProperties properties) {
if (properties.isOwner()) throw new IllegalArgumentException();
return new SimpleApiCall(() -> {
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
// Call the observers and cache the result
onConnectivityCheckSucceeded(clock.currentTimeMillis());
});
return new SimpleApiCall() {
@Override
void tryToCallApi() throws IOException, ApiException {
if (!mailboxApi.checkStatus(properties)) {
throw new ApiException();
}
// Call the observers and cache the result
onConnectivityCheckSucceeded(clock.currentTimeMillis());
}
};
}
}

View File

@@ -1,243 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.mailbox.TorReachabilityMonitor.TorReachabilityObserver;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class ContactMailboxDownloadWorker implements MailboxWorker,
ConnectivityObserver, TorReachabilityObserver {
/**
* When the worker is started it waits for a connectivity check, then
* starts its first download cycle: checking the inbox, downloading and
* deleting any files, and checking again until the inbox is empty.
* <p>
* The worker then waits for our Tor hidden service to be reachable before
* starting its second download cycle. This ensures that if a contact
* tried and failed to connect to our hidden service before it was
* reachable, and therefore uploaded a file to the mailbox instead, we'll
* find the file in the second download cycle.
*/
private enum State {
CREATED,
CONNECTIVITY_CHECK,
DOWNLOAD_CYCLE_1,
WAITING_FOR_TOR,
DOWNLOAD_CYCLE_2,
FINISHED,
DESTROYED
}
private static final Logger LOG =
getLogger(ContactMailboxDownloadWorker.class.getName());
private final ConnectivityChecker connectivityChecker;
private final TorReachabilityMonitor torReachabilityMonitor;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
private final MailboxProperties mailboxProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable apiCall = null;
ContactMailboxDownloadWorker(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor torReachabilityMonitor,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties) {
if (mailboxProperties.isOwner()) throw new IllegalArgumentException();
this.connectivityChecker = connectivityChecker;
this.torReachabilityMonitor = torReachabilityMonitor;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxProperties = mailboxProperties;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
// Don't allow the worker to be reused
if (state != State.CREATED) return;
state = State.CONNECTIVITY_CHECK;
}
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable apiCall;
synchronized (lock) {
state = State.DESTROYED;
apiCall = this.apiCall;
this.apiCall = null;
}
if (apiCall != null) apiCall.cancel();
connectivityChecker.removeObserver(this);
torReachabilityMonitor.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.DOWNLOAD_CYCLE_1;
// Start first download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
}
private void apiCallListInbox() throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Listing inbox");
List<MailboxFile> files = mailboxApi.getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
if (files.isEmpty()) onDownloadCycleFinished();
else downloadNextFile(new LinkedList<>(files));
}
private void onDownloadCycleFinished() {
boolean addObserver = false;
synchronized (lock) {
if (state == State.DOWNLOAD_CYCLE_1) {
LOG.info("First download cycle finished");
state = State.WAITING_FOR_TOR;
apiCall = null;
addObserver = true;
} else if (state == State.DOWNLOAD_CYCLE_2) {
LOG.info("Second download cycle finished");
state = State.FINISHED;
apiCall = null;
}
}
if (addObserver) {
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
torReachabilityMonitor.addOneShotObserver(this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) torReachabilityMonitor.removeObserver(this);
}
}
private void downloadNextFile(Queue<MailboxFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
MailboxFile file = queue.remove();
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDownloadFile(file, queue)));
}
}
private void apiCallDownloadFile(MailboxFile file,
Queue<MailboxFile> queue) throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
LOG.info("Downloading file");
File tempFile = mailboxFileManager.createTempFileForDownload();
try {
mailboxApi.getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file.name, tempFile);
} catch (IOException | ApiException e) {
if (!tempFile.delete()) {
LOG.warning("Failed to delete temporary file");
}
throw e;
}
mailboxFileManager.handleDownloadedFile(tempFile);
deleteFile(file, queue);
}
private void deleteFile(MailboxFile file, Queue<MailboxFile> queue) {
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallDeleteFile(file, queue)));
}
}
private void apiCallDeleteFile(MailboxFile file, Queue<MailboxFile> queue)
throws IOException, ApiException {
synchronized (lock) {
if (state == State.DESTROYED) return;
}
try {
mailboxApi.deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file.name);
} catch (TolerableFailureException e) {
// Catch this so we can continue to the next file
logException(LOG, INFO, e);
}
if (queue.isEmpty()) {
// List the inbox again to check for files that may have arrived
// while we were downloading
synchronized (lock) {
if (state == State.DESTROYED) return;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
} else {
downloadNextFile(queue);
}
}
@Override
public void onTorReachable() {
LOG.info("Our Tor hidden service is reachable");
synchronized (lock) {
if (state != State.WAITING_FOR_TOR) return;
state = State.DOWNLOAD_CYCLE_2;
// Start second download cycle
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallListInbox));
}
}
}

View File

@@ -24,8 +24,6 @@ interface MailboxApiCaller {
* Asynchronously calls the given API call on the {@link IoExecutor},
* automatically retrying at increasing intervals until the API call
* returns false or retries are cancelled.
* <p>
* This method is safe to call while holding a lock.
*
* @return A {@link Cancellable} that can be used to cancel any future
* retries.

View File

@@ -1,46 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
interface MailboxClient {
/**
* Asynchronously starts the client.
*/
void start();
/**
* Destroys the client and its workers, cancelling any pending tasks or
* retries.
*/
void destroy();
/**
* Assigns a contact to the client for upload.
*/
void assignContactForUpload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId);
/**
* Deassigns a contact from the client for upload.
*/
void deassignContactForUpload(ContactId c);
/**
* Assigns a contact to the client for download.
*/
void assignContactForDownload(ContactId c, MailboxProperties properties,
MailboxFolderId folderId);
/**
* Deassigns a contact from the client for download.
*/
void deassignContactForDownload(ContactId c);
}

View File

@@ -1,8 +1,6 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import java.io.File;
import java.io.IOException;
@@ -18,14 +16,6 @@ interface MailboxFileManager {
*/
File createTempFileForDownload() throws IOException;
/**
* Creates a file to be uploaded to the given contact and writes any
* waiting data to the file. The IDs of any messages sent or acked will
* be added to the given {@link OutgoingSessionRecord}.
*/
File createAndWriteTempFileForUpload(ContactId contactId,
OutgoingSessionRecord sessionRecord) throws IOException;
/**
* Handles a file that has been downloaded. The file should be created
* with {@link #createTempFileForDownload()}.

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.mailbox;
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;
@@ -11,18 +10,13 @@ import org.briarproject.bramble.api.mailbox.MailboxDirectory;
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.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -36,7 +30,6 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.util.IoUtils.delete;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@@ -48,7 +41,6 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
// Package access for testing
static final String DOWNLOAD_DIR_NAME = "downloads";
static final String UPLOAD_DIR_NAME = "uploads";
private final Executor ioExecutor;
private final PluginManager pluginManager;
@@ -75,44 +67,14 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
@Override
public File createTempFileForDownload() throws IOException {
return createTempFile(DOWNLOAD_DIR_NAME);
}
@Override
public File createAndWriteTempFileForUpload(ContactId contactId,
OutgoingSessionRecord sessionRecord) throws IOException {
File f = createTempFile(UPLOAD_DIR_NAME);
// We shouldn't reach this point until the plugin has been started
SimplexPlugin plugin =
(SimplexPlugin) requireNonNull(pluginManager.getPlugin(ID));
TransportProperties p = new TransportProperties();
p.put(PROP_PATH, f.getAbsolutePath());
TransportConnectionWriter writer = plugin.createWriter(p);
if (writer == null) {
delete(f);
throw new IOException();
}
MailboxFileWriter decorated = new MailboxFileWriter(writer);
LOG.info("Writing file for upload");
connectionManager.manageOutgoingConnection(contactId, ID, decorated,
sessionRecord);
if (decorated.awaitDisposal()) {
// An exception was thrown during the session - delete the file
delete(f);
throw new IOException();
}
return f;
}
private File createTempFile(String dirName) throws IOException {
// Wait for orphaned files to be handled before creating new files
try {
orphanLatch.await();
} catch (InterruptedException e) {
throw new IOException(e);
}
File dir = createDirectoryIfNeeded(dirName);
return File.createTempFile("mailbox", ".tmp", dir);
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
return File.createTempFile("mailbox", ".tmp", downloadDir);
}
private File createDirectoryIfNeeded(String name) throws IOException {
@@ -154,8 +116,6 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
@Override
public void eventOccurred(Event e) {
// Wait for the transport to become active before handling orphaned
// files so that we can get the plugin from the plugin manager
if (e instanceof TransportActiveEvent) {
TransportActiveEvent t = (TransportActiveEvent) e;
if (t.getTransportId().equals(ID)) {
@@ -167,25 +127,17 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
/**
* This method is called at startup, as soon as the plugin is started, to
* delete any files that were left in the upload directory at the last
* shutdown and handle any files that were left in the download directory.
* handle any files that were left in the download directory at the last
* shutdown.
*/
@IoExecutor
private void handleOrphanedFiles() {
try {
File uploadDir = createDirectoryIfNeeded(UPLOAD_DIR_NAME);
File[] orphanedUploads = uploadDir.listFiles();
if (orphanedUploads != null) {
for (File f : orphanedUploads) delete(f);
}
File downloadDir = createDirectoryIfNeeded(DOWNLOAD_DIR_NAME);
File[] orphanedDownloads = downloadDir.listFiles();
// Now that we've got the list of orphaned downloads, new files
// can be created in the download directory
File[] orphans = downloadDir.listFiles();
// Now that we've got the list of orphans, new files can be created
orphanLatch.countDown();
if (orphanedDownloads != null) {
for (File f : orphanedDownloads) handleDownloadedFile(f);
}
if (orphans != null) for (File f : orphans) handleDownloadedFile(f);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
@@ -213,58 +165,9 @@ class MailboxFileManagerImpl implements MailboxFileManager, EventListener {
delegate.dispose(exception, recognised);
if (isHandlingComplete(exception, recognised)) {
LOG.info("Deleting downloaded file");
delete(file);
}
}
}
private static class MailboxFileWriter
implements TransportConnectionWriter {
private final TransportConnectionWriter delegate;
private final BlockingQueue<Boolean> disposalResult =
new ArrayBlockingQueue<>(1);
private MailboxFileWriter(TransportConnectionWriter delegate) {
this.delegate = delegate;
}
@Override
public long getMaxLatency() {
return delegate.getMaxLatency();
}
@Override
public int getMaxIdleTime() {
return delegate.getMaxIdleTime();
}
@Override
public boolean isLossyAndCheap() {
return delegate.isLossyAndCheap();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public void dispose(boolean exception) throws IOException {
delegate.dispose(exception);
disposalResult.add(exception);
}
/**
* Waits for the delegate to be disposed and returns true if an
* exception occurred.
*/
private boolean awaitDisposal() {
try {
return disposalResult.take();
} catch (InterruptedException e) {
LOG.info("Interrupted while waiting for disposal");
return true;
if (!file.delete()) {
LOG.warning("Failed to delete downloaded file");
}
}
}
}

View File

@@ -9,12 +9,10 @@ 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.MailboxStatus;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
@@ -100,35 +98,34 @@ class MailboxManagerImpl implements MailboxManager {
@Override
public boolean checkConnection() {
List<MailboxVersion> versions = null;
boolean success;
try {
MailboxProperties props = db.transactionWithNullableResult(true,
mailboxSettingsManager::getOwnMailboxProperties);
if (props == null) throw new DbException();
versions = api.getServerSupports(props);
success = api.checkStatus(props);
} catch (DbException e) {
logException(LOG, WARNING, e);
// we don't treat this is a failure to record
return false;
} catch (IOException | MailboxApi.ApiException e) {
// we record this as a failure
success = false;
logException(LOG, WARNING, e);
}
try {
recordCheckResult(versions);
recordCheckResult(success);
} catch (DbException e) {
logException(LOG, WARNING, e);
}
return versions != null;
return success;
}
private void recordCheckResult(@Nullable List<MailboxVersion> versions)
throws DbException {
private void recordCheckResult(boolean success) throws DbException {
long now = clock.currentTimeMillis();
db.transaction(false, txn -> {
if (versions != null) {
mailboxSettingsManager
.recordSuccessfulConnection(txn, now, versions);
if (success) {
mailboxSettingsManager.recordSuccessfulConnection(txn, now);
} else {
mailboxSettingsManager.recordFailedConnectionAttempt(txn, now);
}

View File

@@ -52,7 +52,6 @@ public class MailboxModule {
}
@Provides
@Singleton
MailboxSettingsManager provideMailboxSettingsManager(
MailboxSettingsManagerImpl mailboxSettingsManager) {
return mailboxSettingsManager;
@@ -115,10 +114,4 @@ public class MailboxModule {
}
return mailboxFileManager;
}
@Provides
MailboxWorkerFactory provideMailboxWorkerFactory(
MailboxWorkerFactoryImpl mailboxWorkerFactory) {
return mailboxWorkerFactory;
}
}

View File

@@ -20,13 +20,13 @@ import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ThreadSafe
@Immutable
@NotNullByDefault
class MailboxSettingsManagerImpl implements MailboxSettingsManager {
@@ -77,7 +77,13 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
s.put(SETTINGS_KEY_ONION, p.getBaseUrl());
s.put(SETTINGS_KEY_TOKEN, p.getAuthToken().toString());
List<MailboxVersion> serverSupports = p.getServerSupports();
encodeServerSupports(serverSupports, s);
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);
for (MailboxHook hook : hooks) {
hook.mailboxPaired(txn, p.getOnion(), p.getServerSupports());
@@ -115,30 +121,14 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
@Override
public void recordSuccessfulConnection(Transaction txn, long now)
throws DbException {
recordSuccessfulConnection(txn, now, null);
}
@Override
public void recordSuccessfulConnection(Transaction txn, long now,
@Nullable List<MailboxVersion> versions) throws DbException {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
Settings s = new Settings();
// fetch version that the server supports first
List<MailboxVersion> serverSupports;
if (versions == null) {
Settings oldSettings =
settingsManager.getSettings(txn, SETTINGS_NAMESPACE);
serverSupports = parseServerSupports(oldSettings);
} else {
serverSupports = versions;
// store new versions
encodeServerSupports(serverSupports, s);
}
// now record the successful connection
s.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
s.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
s.putInt(SETTINGS_KEY_ATTEMPTS, 0);
settingsManager.mergeSettings(txn, s, SETTINGS_NAMESPACE);
// broadcast status event
List<MailboxVersion> serverSupports = parseServerSupports(oldSettings);
MailboxStatus status = new MailboxStatus(now, now, 0, serverSupports);
txn.attach(new OwnMailboxConnectionStatusEvent(status));
}
@@ -181,17 +171,6 @@ class MailboxSettingsManagerImpl implements MailboxSettingsManager {
return filename;
}
private void encodeServerSupports(List<MailboxVersion> serverSupports,
Settings s) {
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);
}
private List<MailboxVersion> parseServerSupports(Settings s)
throws DbException {
if (!s.containsKey(SETTINGS_KEY_SERVER_SUPPORTS)) return emptyList();

View File

@@ -1,394 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
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.EventExecutor;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
import org.briarproject.bramble.api.sync.event.MessageToAckEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
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.mailbox.MailboxConstants.MAX_LATENCY;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.util.IoUtils.delete;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class MailboxUploadWorker implements MailboxWorker, ConnectivityObserver,
EventListener {
/**
* When the worker is started it checks for data to send. If data is ready
* to send, the worker waits for a connectivity check, then writes and
* uploads a file and checks again for data to send.
* <p>
* If data is due to be sent at some time in the future, the worker
* schedules a wakeup for that time and also listens for events indicating
* that new data may be ready to send.
* <p>
* If there's no data to send, the worker listens for events indicating
* that new data may be ready to send.
*/
private enum State {
CREATED,
CHECKING_FOR_DATA,
WAITING_FOR_DATA,
CONNECTIVITY_CHECK,
WRITING_UPLOADING,
DESTROYED
}
private static final Logger LOG =
getLogger(MailboxUploadWorker.class.getName());
/**
* When we're waiting for data to send and an event indicates that new data
* may have become available, wait this long before checking the DB. This
* should help to avoid creating lots of small files when several acks or
* messages become available to send in a short period (eg when reading a
* file downloaded from a mailbox).
* <p>
* Package access for testing.
*/
static final long CHECK_DELAY_MS = 5_000;
/**
* How long to wait before retrying when an exception occurs while writing
* a file.
* <p>
* Package access for testing.
*/
static final long RETRY_DELAY_MS = MINUTES.toMillis(1);
private final Executor ioExecutor;
private final DatabaseComponent db;
private final Clock clock;
private final TaskScheduler taskScheduler;
private final EventBus eventBus;
private final ConnectivityChecker connectivityChecker;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
private final MailboxProperties mailboxProperties;
private final MailboxFolderId folderId;
private final ContactId contactId;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable wakeupTask = null, checkTask = null, apiCall = null;
@GuardedBy("lock")
@Nullable
private File file = null;
MailboxUploadWorker(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
Clock clock,
TaskScheduler taskScheduler,
EventBus eventBus,
ConnectivityChecker connectivityChecker,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager,
MailboxProperties mailboxProperties,
MailboxFolderId folderId,
ContactId contactId) {
this.ioExecutor = ioExecutor;
this.db = db;
this.clock = clock;
this.taskScheduler = taskScheduler;
this.eventBus = eventBus;
this.connectivityChecker = connectivityChecker;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
this.mailboxProperties = mailboxProperties;
this.folderId = folderId;
this.contactId = contactId;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
// Don't allow the worker to be reused
if (state != State.CREATED) return;
state = State.CHECKING_FOR_DATA;
}
ioExecutor.execute(this::checkForDataToSend);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable wakeupTask, checkTask, apiCall;
File file;
synchronized (lock) {
state = State.DESTROYED;
wakeupTask = this.wakeupTask;
this.wakeupTask = null;
checkTask = this.checkTask;
this.checkTask = null;
apiCall = this.apiCall;
this.apiCall = null;
file = this.file;
this.file = null;
}
if (wakeupTask != null) wakeupTask.cancel();
if (checkTask != null) checkTask.cancel();
if (apiCall != null) apiCall.cancel();
if (file != null) delete(file);
connectivityChecker.removeObserver(this);
eventBus.removeListener(this);
}
@IoExecutor
private void checkForDataToSend() {
synchronized (lock) {
checkTask = null;
if (state != State.CHECKING_FOR_DATA) return;
}
LOG.info("Checking for data to send");
try {
db.transaction(true, txn -> {
long nextSendTime;
if (db.containsAcksToSend(txn, contactId)) {
nextSendTime = 0L;
} else {
nextSendTime = db.getNextSendTime(txn, contactId,
MAX_LATENCY);
}
// Handle the result on the event executor to avoid races with
// incoming events
txn.attach(() -> handleNextSendTime(nextSendTime));
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@EventExecutor
private void handleNextSendTime(long nextSendTime) {
if (nextSendTime == Long.MAX_VALUE) {
// Nothing is sendable now or due to be sent in the future. Wait
// for an event indicating that new data may be ready to send
waitForDataToSend();
} else {
// Work out the delay until data's ready to send (may be negative)
long delay = nextSendTime - clock.currentTimeMillis();
if (delay > 0) {
// Schedule a wakeup when data will be ready to send. If an
// event is received in the meantime indicating that new data
// may be ready to send, we'll cancel the wakeup
scheduleWakeup(delay);
} else {
// Data is ready to send now
checkConnectivity();
}
}
}
@EventExecutor
private void waitForDataToSend() {
synchronized (lock) {
if (state != State.CHECKING_FOR_DATA) return;
state = State.WAITING_FOR_DATA;
LOG.info("Waiting for data to send");
}
}
@EventExecutor
private void scheduleWakeup(long delay) {
synchronized (lock) {
if (state != State.CHECKING_FOR_DATA) return;
state = State.WAITING_FOR_DATA;
if (LOG.isLoggable(INFO)) {
LOG.info("Scheduling wakeup in " + delay + " ms");
}
wakeupTask = taskScheduler.schedule(this::wakeUp, ioExecutor,
delay, MILLISECONDS);
}
}
@IoExecutor
private void wakeUp() {
LOG.info("Woke up");
synchronized (lock) {
wakeupTask = null;
if (state != State.WAITING_FOR_DATA) return;
state = State.CHECKING_FOR_DATA;
}
checkForDataToSend();
}
@EventExecutor
private void checkConnectivity() {
synchronized (lock) {
if (state != State.CHECKING_FOR_DATA) return;
state = State.CONNECTIVITY_CHECK;
}
LOG.info("Checking connectivity");
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.WRITING_UPLOADING;
}
ioExecutor.execute(this::writeAndUploadFile);
}
@IoExecutor
private void writeAndUploadFile() {
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
}
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
File file;
try {
file = mailboxFileManager.createAndWriteTempFileForUpload(
contactId, sessionRecord);
} catch (IOException e) {
logException(LOG, WARNING, e);
// Try again after a delay
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
state = State.CHECKING_FOR_DATA;
checkTask = taskScheduler.schedule(this::checkForDataToSend,
ioExecutor, RETRY_DELAY_MS, MILLISECONDS);
}
return;
}
boolean deleteFile = false;
synchronized (lock) {
if (state == State.WRITING_UPLOADING) {
this.file = file;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(() -> apiCallUploadFile(file,
sessionRecord)));
} else {
deleteFile = true;
}
}
if (deleteFile) delete(file);
}
@IoExecutor
private void apiCallUploadFile(File file,
OutgoingSessionRecord sessionRecord)
throws IOException, ApiException {
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
}
LOG.info("Uploading file");
mailboxApi.addFile(mailboxProperties, folderId, file);
markMessagesSentOrAcked(sessionRecord);
synchronized (lock) {
if (state != State.WRITING_UPLOADING) return;
state = State.CHECKING_FOR_DATA;
apiCall = null;
this.file = null;
}
delete(file);
checkForDataToSend();
}
private void markMessagesSentOrAcked(OutgoingSessionRecord sessionRecord) {
Collection<MessageId> acked = sessionRecord.getAckedIds();
Collection<MessageId> sent = sessionRecord.getSentIds();
try {
db.transaction(false, txn -> {
if (!acked.isEmpty()) {
db.setAckSent(txn, contactId, acked);
}
if (!sent.isEmpty()) {
db.setMessagesSent(txn, contactId, sent, MAX_LATENCY);
}
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@Override
public void eventOccurred(Event e) {
if (e instanceof MessageToAckEvent) {
MessageToAckEvent m = (MessageToAckEvent) e;
if (m.getContactId().equals(contactId)) {
LOG.info("Message to ack");
onDataToSend();
}
} else if (e instanceof MessageSharedEvent) {
LOG.info("Message shared");
onDataToSend();
} else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getVisibility() == SHARED &&
g.getAffectedContacts().contains(contactId)) {
LOG.info("Group shared");
onDataToSend();
}
}
}
@EventExecutor
private void onDataToSend() {
Cancellable wakeupTask;
synchronized (lock) {
if (state != State.WAITING_FOR_DATA) return;
state = State.CHECKING_FOR_DATA;
wakeupTask = this.wakeupTask;
this.wakeupTask = null;
// Delay the check to avoid creating lots of small files
checkTask = taskScheduler.schedule(this::checkForDataToSend,
ioExecutor, CHECK_DELAY_MS, MILLISECONDS);
}
// If we had scheduled a wakeup when data was due to be sent, cancel it
if (wakeupTask != null) wakeupTask.cancel();
}
}

View File

@@ -1,23 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
/**
* A worker that downloads files from a contact's mailbox.
*/
@ThreadSafe
@NotNullByDefault
interface MailboxWorker {
/**
* Asynchronously starts the worker.
*/
void start();
/**
* Destroys the worker and cancels any pending tasks or retries.
*/
void destroy();
}

View File

@@ -1,27 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.ThreadSafe;
@ThreadSafe
@NotNullByDefault
interface MailboxWorkerFactory {
MailboxWorker createUploadWorker(ConnectivityChecker connectivityChecker,
MailboxProperties properties, MailboxFolderId folderId,
ContactId contactId);
MailboxWorker createDownloadWorkerForContactMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties);
MailboxWorker createDownloadWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties);
}

View File

@@ -1,81 +0,0 @@
package org.briarproject.bramble.mailbox;
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.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import java.util.concurrent.Executor;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
@Immutable
@NotNullByDefault
class MailboxWorkerFactoryImpl implements MailboxWorkerFactory {
private final Executor ioExecutor;
private final DatabaseComponent db;
private final Clock clock;
private final TaskScheduler taskScheduler;
private final EventBus eventBus;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxFileManager mailboxFileManager;
@Inject
MailboxWorkerFactoryImpl(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
Clock clock,
TaskScheduler taskScheduler,
EventBus eventBus,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxFileManager mailboxFileManager) {
this.ioExecutor = ioExecutor;
this.db = db;
this.clock = clock;
this.taskScheduler = taskScheduler;
this.eventBus = eventBus;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxFileManager = mailboxFileManager;
}
@Override
public MailboxWorker createUploadWorker(
ConnectivityChecker connectivityChecker,
MailboxProperties properties, MailboxFolderId folderId,
ContactId contactId) {
MailboxUploadWorker worker = new MailboxUploadWorker(ioExecutor, db,
clock, taskScheduler, eventBus, connectivityChecker,
mailboxApiCaller, mailboxApi, mailboxFileManager,
properties, folderId, contactId);
eventBus.addListener(worker);
return worker;
}
@Override
public MailboxWorker createDownloadWorkerForContactMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties) {
return new ContactMailboxDownloadWorker(connectivityChecker,
reachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, properties);
}
@Override
public MailboxWorker createDownloadWorkerForOwnMailbox(
ConnectivityChecker connectivityChecker,
TorReachabilityMonitor reachabilityMonitor,
MailboxProperties properties) {
// TODO
throw new UnsupportedOperationException();
}
}

View File

@@ -4,13 +4,11 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.ThreadSafe;
@@ -57,12 +55,11 @@ class OwnMailboxConnectivityChecker extends ConnectivityCheckerImpl {
private boolean checkConnectivityAndStoreResult(
MailboxProperties properties) throws DbException {
try {
List<MailboxVersion> serverSupports =
mailboxApi.getServerSupports(properties);
if (!mailboxApi.checkStatus(properties)) throw new ApiException();
LOG.info("Own mailbox is reachable");
long now = clock.currentTimeMillis();
db.transaction(false, txn -> mailboxSettingsManager
.recordSuccessfulConnection(txn, now, serverSupports));
.recordSuccessfulConnection(txn, now));
// Call the observers and cache the result
onConnectivityCheckSucceeded(now);
return false; // Don't retry

View File

@@ -1,369 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventExecutor;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.mailbox.MailboxApi.ApiException;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
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.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.util.LogUtils.logException;
@ThreadSafe
@NotNullByDefault
class OwnMailboxContactListWorker
implements MailboxWorker, ConnectivityObserver, EventListener {
/**
* When the worker is started it waits for a connectivity check, then
* fetches the remote contact list and compares it to the local contact
* list.
* <p>
* Any contacts that are missing from the remote list are added to the
* mailbox's contact list, while any contacts that are missing from the
* local list are removed from the mailbox's contact list.
* <p>
* Once the remote contact list has been brought up to date, the worker
* waits for events indicating that contacts have been added or removed.
* Each time an event is received, the worker updates the mailbox's
* contact list and then goes back to waiting.
*/
private enum State {
CREATED,
CONNECTIVITY_CHECK,
FETCHING_CONTACT_LIST,
UPDATING_CONTACT_LIST,
WAITING_FOR_CHANGES,
DESTROYED
}
private static final Logger LOG =
getLogger(OwnMailboxContactListWorker.class.getName());
private final Executor ioExecutor;
private final DatabaseComponent db;
private final EventBus eventBus;
private final ConnectivityChecker connectivityChecker;
private final MailboxApiCaller mailboxApiCaller;
private final MailboxApi mailboxApi;
private final MailboxUpdateManager mailboxUpdateManager;
private final MailboxProperties mailboxProperties;
private final Object lock = new Object();
@GuardedBy("lock")
private State state = State.CREATED;
@GuardedBy("lock")
@Nullable
private Cancellable apiCall = null;
/**
* A queue of updates waiting to be applied to the remote contact list.
*/
@GuardedBy("lock")
private final Queue<Update> updates = new LinkedList<>();
OwnMailboxContactListWorker(@IoExecutor Executor ioExecutor,
DatabaseComponent db,
EventBus eventBus,
ConnectivityChecker connectivityChecker,
MailboxApiCaller mailboxApiCaller,
MailboxApi mailboxApi,
MailboxUpdateManager mailboxUpdateManager,
MailboxProperties mailboxProperties) {
if (!mailboxProperties.isOwner()) throw new IllegalArgumentException();
this.ioExecutor = ioExecutor;
this.db = db;
this.connectivityChecker = connectivityChecker;
this.mailboxApiCaller = mailboxApiCaller;
this.mailboxApi = mailboxApi;
this.mailboxUpdateManager = mailboxUpdateManager;
this.mailboxProperties = mailboxProperties;
this.eventBus = eventBus;
}
@Override
public void start() {
LOG.info("Started");
synchronized (lock) {
if (state != State.CREATED) return;
state = State.CONNECTIVITY_CHECK;
}
// Avoid leaking observer in case destroy() is called concurrently
// before observer is added
connectivityChecker.checkConnectivity(mailboxProperties, this);
boolean destroyed;
synchronized (lock) {
destroyed = state == State.DESTROYED;
}
if (destroyed) connectivityChecker.removeObserver(this);
}
@Override
public void destroy() {
LOG.info("Destroyed");
Cancellable apiCall;
synchronized (lock) {
state = State.DESTROYED;
apiCall = this.apiCall;
this.apiCall = null;
}
if (apiCall != null) apiCall.cancel();
connectivityChecker.removeObserver(this);
eventBus.removeListener(this);
}
@Override
public void onConnectivityCheckSucceeded() {
LOG.info("Connectivity check succeeded");
synchronized (lock) {
if (state != State.CONNECTIVITY_CHECK) return;
state = State.FETCHING_CONTACT_LIST;
apiCall = mailboxApiCaller.retryWithBackoff(
new SimpleApiCall(this::apiCallFetchContactList));
}
}
@IoExecutor
private void apiCallFetchContactList() throws IOException, ApiException {
synchronized (lock) {
if (state != State.FETCHING_CONTACT_LIST) return;
}
LOG.info("Fetching remote contact list");
Collection<ContactId> remote =
mailboxApi.getContacts(mailboxProperties);
ioExecutor.execute(() -> loadLocalContactList(remote));
}
@IoExecutor
private void loadLocalContactList(Collection<ContactId> remote) {
synchronized (lock) {
if (state != State.FETCHING_CONTACT_LIST) return;
apiCall = null;
}
LOG.info("Loading local contact list");
try {
db.transaction(true, txn -> {
Collection<Contact> local = db.getContacts(txn);
// Handle the result on the event executor to avoid races with
// incoming events
txn.attach(() -> reconcileContactLists(local, remote));
});
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@EventExecutor
private void reconcileContactLists(Collection<Contact> local,
Collection<ContactId> remote) {
Set<ContactId> localIds = new HashSet<>();
for (Contact c : local) localIds.add(c.getId());
remote = new HashSet<>(remote);
synchronized (lock) {
if (state != State.FETCHING_CONTACT_LIST) return;
for (ContactId c : localIds) {
if (!remote.contains(c)) updates.add(new Update(true, c));
}
for (ContactId c : remote) {
if (!localIds.contains(c)) updates.add(new Update(false, c));
}
if (updates.isEmpty()) {
LOG.info("Contact list is up to date");
state = State.WAITING_FOR_CHANGES;
} else {
if (LOG.isLoggable(INFO)) {
LOG.info(updates.size() + " updates to apply");
}
state = State.UPDATING_CONTACT_LIST;
ioExecutor.execute(this::updateContactList);
}
}
}
@IoExecutor
private void updateContactList() {
Update update;
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
update = updates.poll();
if (update == null) {
LOG.info("No more updates to process");
state = State.WAITING_FOR_CHANGES;
apiCall = null;
return;
}
}
if (update.add) loadMailboxProperties(update.contactId);
else removeContact(update.contactId);
}
@IoExecutor
private void loadMailboxProperties(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
}
LOG.info("Loading mailbox properties for contact");
try {
MailboxUpdate mailboxUpdate = db.transactionWithResult(true, txn ->
mailboxUpdateManager.getLocalUpdate(txn, c));
if (mailboxUpdate instanceof MailboxUpdateWithMailbox) {
addContact(c, (MailboxUpdateWithMailbox) mailboxUpdate);
} else {
// Our own mailbox was concurrently unpaired. This worker will
// be destroyed soon, so we can stop here
LOG.info("Own mailbox was unpaired");
}
} catch (NoSuchContactException e) {
// Contact was removed concurrently. Move on to the next update.
// Later we may process a removal update for this contact, which
// was never added to the mailbox's contact list. The removal API
// call should fail safely with a TolerableFailureException
LOG.info("No such contact");
updateContactList();
} catch (DbException e) {
logException(LOG, WARNING, e);
}
}
@IoExecutor
private void addContact(ContactId c, MailboxUpdateWithMailbox withMailbox) {
MailboxProperties props = withMailbox.getMailboxProperties();
MailboxContact contact = new MailboxContact(c, props.getAuthToken(),
requireNonNull(props.getInboxId()),
requireNonNull(props.getOutboxId()));
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
apiCallAddContact(contact)));
}
}
@IoExecutor
private void apiCallAddContact(MailboxContact contact)
throws IOException, ApiException, TolerableFailureException {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
}
LOG.info("Adding contact to remote contact list");
mailboxApi.addContact(mailboxProperties, contact);
updateContactList();
}
@IoExecutor
private void removeContact(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
apiCall = mailboxApiCaller.retryWithBackoff(new SimpleApiCall(() ->
apiCallRemoveContact(c)));
}
}
@IoExecutor
private void apiCallRemoveContact(ContactId c)
throws IOException, ApiException {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST) return;
}
LOG.info("Removing contact from remote contact list");
try {
mailboxApi.deleteContact(mailboxProperties, c);
} catch (TolerableFailureException e) {
// Catch this so we can continue to the next update
logException(LOG, INFO, e);
}
updateContactList();
}
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added");
onContactAdded(((ContactAddedEvent) e).getContactId());
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed");
onContactRemoved(((ContactRemovedEvent) e).getContactId());
}
}
@EventExecutor
private void onContactAdded(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST &&
state != State.WAITING_FOR_CHANGES) {
return;
}
updates.add(new Update(true, c));
if (state == State.WAITING_FOR_CHANGES) {
state = State.UPDATING_CONTACT_LIST;
ioExecutor.execute(this::updateContactList);
}
}
}
@EventExecutor
private void onContactRemoved(ContactId c) {
synchronized (lock) {
if (state != State.UPDATING_CONTACT_LIST &&
state != State.WAITING_FOR_CHANGES) {
return;
}
updates.add(new Update(false, c));
if (state == State.WAITING_FOR_CHANGES) {
state = State.UPDATING_CONTACT_LIST;
ioExecutor.execute(this::updateContactList);
}
}
}
/**
* An update that should be applied to the remote contact list.
*/
private static class Update {
/**
* True if the contact should be added, false if the contact should be
* removed.
*/
private final boolean add;
private final ContactId contactId;
private Update(boolean add, ContactId contactId) {
this.add = add;
this.contactId = contactId;
}
}
}

View File

@@ -16,20 +16,17 @@ import static org.briarproject.bramble.util.LogUtils.logException;
* Convenience class for making simple API calls that don't return values.
*/
@NotNullByDefault
class SimpleApiCall implements ApiCall {
public abstract class SimpleApiCall implements ApiCall {
private static final Logger LOG = getLogger(SimpleApiCall.class.getName());
private final Attempt attempt;
SimpleApiCall(Attempt attempt) {
this.attempt = attempt;
}
abstract void tryToCallApi()
throws IOException, ApiException, TolerableFailureException;
@Override
public boolean callApi() {
try {
attempt.tryToCallApi();
tryToCallApi();
return false; // Succeeded, don't retry
} catch (IOException | ApiException e) {
logException(LOG, WARNING, e);
@@ -39,17 +36,4 @@ class SimpleApiCall implements ApiCall {
return false; // Failed tolerably, don't retry
}
}
interface Attempt {
/**
* Makes a single attempt to call an API endpoint. If this method
* throws an {@link IOException} or an {@link ApiException}, the call
* will be retried. If it throws a {@link TolerableFailureException}
* or returns without throwing an exception, the call will not be
* retried.
*/
void tryToCallApi()
throws IOException, ApiException, TolerableFailureException;
}
}

View File

@@ -38,12 +38,6 @@ interface TorReachabilityMonitor {
*/
void addOneShotObserver(TorReachabilityObserver o);
/**
* Removes an observer that was added via
* {@link #addOneShotObserver(TorReachabilityObserver)}.
*/
void removeObserver(TorReachabilityObserver o);
interface TorReachabilityObserver {
void onTorReachable();

View File

@@ -87,14 +87,6 @@ class TorReachabilityMonitorImpl
if (callNow) o.onTorReachable();
}
@Override
public void removeObserver(TorReachabilityObserver o) {
synchronized (lock) {
if (destroyed) return;
observers.remove(o);
}
}
@Override
public void eventOccurred(Event e) {
if (e instanceof TransportActiveEvent) {

View File

@@ -9,12 +9,14 @@ import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import javax.annotation.Nullable;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.DAYS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
@NotNullByDefault
public class MailboxPluginFactory implements SimplexPluginFactory {
private static final long MAX_LATENCY = DAYS.toMillis(14);
@Inject
MailboxPluginFactory() {
}

View File

@@ -106,8 +106,7 @@ class RemovableDriveManagerImpl
@Override
public boolean isWriterTaskNeeded(ContactId c) throws DbException {
return db.transactionWithResult(true, txn ->
db.containsAcksToSend(txn, c) ||
db.containsMessagesToSend(txn, c, MAX_LATENCY, true));
db.containsAnythingToSend(txn, c, MAX_LATENCY, true));
}
@Override

View File

@@ -27,7 +27,7 @@ public interface CircumventionProvider {
* Countries where bridge connections are likely to work.
* Should be a subset of {@link #BLOCKED} and the union of
* {@link #DEFAULT_BRIDGES}, {@link #NON_DEFAULT_BRIDGES} and
* {@link #DPI_BRIDGES}.
* {@link #MEEK_BRIDGES}.
*/
String[] BRIDGES = {"BY", "CN", "EG", "IR", "RU", "TM", "VE"};
@@ -44,10 +44,10 @@ public interface CircumventionProvider {
String[] NON_DEFAULT_BRIDGES = {"BY", "RU", "TM"};
/**
* Countries where vanilla bridges are blocked via DPI but non-default
* obfs4 bridges and meek may work. Should be a subset of {@link #BRIDGES}.
* Countries where obfs4 and vanilla bridges won't work and meek is needed.
* Should be a subset of {@link #BRIDGES}.
*/
String[] DPI_BRIDGES = {"CN", "IR"};
String[] MEEK_BRIDGES = {"CN", "IR"};
/**
* Returns true if vanilla Tor connections are blocked in the given country.

View File

@@ -14,6 +14,7 @@ import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.MEEK;
@@ -34,8 +35,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
new HashSet<>(asList(DEFAULT_BRIDGES));
private static final Set<String> NON_DEFAULT_BRIDGE_COUNTRIES =
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
private static final Set<String> DPI_COUNTRIES =
new HashSet<>(asList(DPI_BRIDGES));
private static final Set<String> MEEK_COUNTRIES =
new HashSet<>(asList(MEEK_BRIDGES));
@Inject
CircumventionProviderImpl() {
@@ -57,8 +58,8 @@ class CircumventionProviderImpl implements CircumventionProvider {
return asList(DEFAULT_OBFS4, VANILLA);
} else if (NON_DEFAULT_BRIDGE_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, VANILLA);
} else if (DPI_COUNTRIES.contains(countryCode)) {
return asList(NON_DEFAULT_OBFS4, MEEK);
} else if (MEEK_COUNTRIES.contains(countryCode)) {
return singletonList(MEEK);
} else {
return asList(DEFAULT_OBFS4, VANILLA);
}

View File

@@ -107,7 +107,7 @@ import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
@ParametersNotNullByDefault
abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
protected static final Logger LOG = getLogger(TorPlugin.class.getName());
private static final Logger LOG = getLogger(TorPlugin.class.getName());
private static final String[] EVENTS = {
"CIRC",
@@ -124,8 +124,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
private static final int COOKIE_POLLING_INTERVAL_MS = 200;
private static final Pattern ONION_V3 = Pattern.compile("[a-z2-7]{56}");
protected final Executor ioExecutor;
private final Executor wakefulIoExecutor;
private final Executor ioExecutor, wakefulIoExecutor;
private final Executor connectionStatusExecutor;
private final NetworkManager networkManager;
private final LocationUtils locationUtils;
@@ -255,15 +254,34 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
Map<String, String> env = pb.environment();
env.put("HOME", torDirectory.getAbsolutePath());
pb.directory(torDirectory);
pb.redirectErrorStream(true);
try {
torProcess = pb.start();
} catch (SecurityException | IOException e) {
throw new PluginException(e);
}
// Log the process's standard output until it detaches
if (LOG.isLoggable(INFO)) {
Scanner stdout = new Scanner(torProcess.getInputStream());
Scanner stderr = new Scanner(torProcess.getErrorStream());
while (stdout.hasNextLine() || stderr.hasNextLine()) {
if (stdout.hasNextLine()) {
LOG.info(stdout.nextLine());
}
if (stderr.hasNextLine()) {
LOG.info(stderr.nextLine());
}
}
stdout.close();
stderr.close();
}
try {
// Wait for the Tor process to start
waitForTorToStart(torProcess);
// Wait for the process to detach or exit
int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
}
// Wait for the auth cookie file to be created/updated
long start = clock.currentTimeMillis();
while (cookieFile.length() < 32) {
@@ -379,7 +397,7 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
return zin;
}
private static void append(StringBuilder strb, String name, Object value) {
private static void append(StringBuilder strb, String name, int value) {
strb.append(name);
strb.append(" ");
strb.append(value);
@@ -387,17 +405,13 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
private InputStream getConfigInputStream() {
File dataDirectory = new File(torDirectory, ".tor");
StringBuilder strb = new StringBuilder();
append(strb, "ControlPort", torControlPort);
append(strb, "CookieAuthentication", 1);
append(strb, "DataDirectory", dataDirectory.getAbsolutePath());
append(strb, "DisableNetwork", 1);
append(strb, "RunAsDaemon", 1);
append(strb, "SafeSocks", 1);
append(strb, "SocksPort", torSocksPort);
strb.append("GeoIPFile\n");
strb.append("GeoIPv6File\n");
//noinspection CharsetObjectCanBeUsed
return new ByteArrayInputStream(
strb.toString().getBytes(Charset.forName("UTF-8")));
@@ -428,23 +442,6 @@ abstract class TorPlugin implements DuplexPlugin, EventHandler, EventListener {
}
}
protected void waitForTorToStart(Process torProcess)
throws InterruptedException, PluginException {
Scanner stdout = new Scanner(torProcess.getInputStream());
// Log the first line of stdout (contains Tor and library versions)
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
// Read the process's stdout (and redirected stderr) until it detaches
while (stdout.hasNextLine()) stdout.nextLine();
stdout.close();
// Wait for the process to detach or exit
int exit = torProcess.waitFor();
if (exit != 0) {
if (LOG.isLoggable(WARNING))
LOG.warning("Tor exited with value " + exit);
throw new PluginException();
}
}
private void bind() {
ioExecutor.execute(() -> {
// If there's already a port number stored in config, reuse it

View File

@@ -50,7 +50,6 @@ import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STOPPING;
import static org.briarproject.bramble.api.record.Record.RECORD_HEADER_BYTES;
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
import static org.briarproject.bramble.api.sync.SyncConstants.SUPPORTED_VERSIONS;
@@ -236,10 +235,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
generateOffer();
} else if (e instanceof GroupVisibilityUpdatedEvent) {
GroupVisibilityUpdatedEvent g = (GroupVisibilityUpdatedEvent) e;
if (g.getVisibility() == SHARED &&
g.getAffectedContacts().contains(contactId)) {
if (g.getAffectedContacts().contains(contactId))
generateOffer();
}
} else if (e instanceof MessageRequestedEvent) {
if (((MessageRequestedEvent) e).getContactId().equals(contactId))
generateBatch();
@@ -313,8 +310,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Collection<Message> batch =
db.generateRequestedBatch(txn, contactId,
BATCH_CAPACITY, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId,
maxLatency));
setNextSendTime(db.getNextSendTime(txn, contactId));
return batch;
});
if (LOG.isLoggable(INFO))
@@ -357,8 +353,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
Offer o = db.transactionWithNullableResult(false, txn -> {
Offer offer = db.generateOffer(txn, contactId,
MAX_MESSAGE_IDS, maxLatency);
setNextSendTime(db.getNextSendTime(txn, contactId,
maxLatency));
setNextSendTime(db.getNextSendTime(txn, contactId));
return offer;
});
if (LOG.isLoggable(INFO))

View File

@@ -7,9 +7,9 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.DeferredSendHandler;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.transport.StreamWriter;
@@ -29,7 +29,7 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
/**
* A {@link SimplexOutgoingSession} for sending and acking messages via a
* mailbox. The session uses a {@link OutgoingSessionRecord} to record the IDs
* mailbox. The session uses a {@link DeferredSendHandler} to record the IDs
* of the messages sent and acked during the session so that they can be
* recorded in the DB as sent or acked after the file has been successfully
* uploaded to the mailbox.
@@ -41,7 +41,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
private static final Logger LOG =
getLogger(MailboxOutgoingSession.class.getName());
private final OutgoingSessionRecord sessionRecord;
private final DeferredSendHandler deferredSendHandler;
private final long initialCapacity;
MailboxOutgoingSession(DatabaseComponent db,
@@ -51,11 +51,11 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
long maxLatency,
StreamWriter streamWriter,
SyncRecordWriter recordWriter,
OutgoingSessionRecord sessionRecord,
DeferredSendHandler deferredSendHandler,
long capacity) {
super(db, eventBus, contactId, transportId, maxLatency, streamWriter,
recordWriter);
this.sessionRecord = sessionRecord;
this.deferredSendHandler = deferredSendHandler;
this.initialCapacity = capacity;
}
@@ -65,7 +65,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
Collection<MessageId> idsToAck = loadMessageIdsToAck();
if (idsToAck.isEmpty()) break;
recordWriter.writeAck(new Ack(idsToAck));
sessionRecord.onAckSent(idsToAck);
deferredSendHandler.onAckSent(idsToAck);
LOG.info("Sent ack");
}
}
@@ -96,7 +96,7 @@ class MailboxOutgoingSession extends SimplexOutgoingSession {
db.getMessageToSend(txn, contactId, m, maxLatency, false));
if (message == null) continue; // No longer shared
recordWriter.writeMessage(message);
sessionRecord.onMessageSent(m);
deferredSendHandler.onMessageSent(m);
LOG.info("Sent message");
}
}

View File

@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.Priority;
import org.briarproject.bramble.api.sync.PriorityHandler;
import org.briarproject.bramble.api.sync.SyncRecordReader;
@@ -26,8 +25,6 @@ import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_FILE_PAYLOAD_BYTES;
@Immutable
@NotNullByDefault
class SyncSessionFactoryImpl implements SyncSessionFactory {
@@ -76,18 +73,6 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
}
}
@Override
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, StreamWriter streamWriter,
OutgoingSessionRecord sessionRecord) {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new MailboxOutgoingSession(db, eventBus, c, t, maxLatency,
streamWriter, recordWriter, sessionRecord,
MAX_FILE_PAYLOAD_BYTES);
}
@Override
public SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
long maxLatency, int maxIdleTime, StreamWriter streamWriter,

View File

@@ -15,19 +15,16 @@ n Bridge obfs4 46.226.107.197:10300 A38FD6BDFD902882F5F5B9B7CCC95602A20B0BC4 cer
n Bridge obfs4 185.181.11.86:443 A961609729E7FDF520B4E81F1F1B8FA1045285C3 cert=e5faG9Zk4Ni+e7z2YgGfevyKPQlMvkVGi4ublSsHYjaBovKeNXpOhbeFxzbZZoAzxAoGUQ iat-mode=0
n Bridge obfs4 85.242.211.221:8042 A36A938DD7FDB8BACC846BA326EE0BA0D89A9252 cert=1AN6Pt1eFca3Y/WYD2TGAU3Al9cO4eouXE9SX63s66Z/ks3tVmgQ5GeXi1B5DOvx6Il7Zw iat-mode=0
n Bridge obfs4 74.104.165.202:9002 EF432018A6AA5D970B2F84E39CD30A147030141C cert=PhppfUusY85dHGvWtGTybZ1fED4DtbHmALkNMIOIYrAz1B4xN7/2a5gyiZe1epju1BOHVg iat-mode=0
n Bridge obfs4 70.34.242.31:443 7F026956402CDFF4BCBA8E11EE9C50E3FE0A2B72 cert=hP/KU7JATSfWH3HwS5Er/YLT0J+bRO3+s2fWx2yirrgf37EyrWvm/BQshoNje8WfUm6CBw iat-mode=0
n Bridge obfs4 93.95.226.151:41185 460B0CFFC0CF1D965F3DE064E08BA1915E7C916A cert=inluPzp5Jp5OzZar1eQb4dcQ/YlAj/v0kHAUCoCr3rmLt03+pVuVTjoH4mRy4+acXpn+Gw iat-mode=0
n Bridge obfs4 120.29.217.52:5223 40FE3DB9800272F9CDC76422F8ED7883280EE96D cert=/71PS4l8c/XJ4DIItlH9xMqNvPFg2RUTrHvPlQWh48u5et8h/yyyjCcYphUadDsfBWpaGQ iat-mode=0
n Bridge obfs4 83.97.179.29:1199 D83068BFAA28E71DB024B786E1E803BE14257127 cert=IduGtt05tM59Xmvo0oVNWgIRgY4OGPJjFP+y2oa6RMDHQBL/GRyFOOgX70iiazNAIJNkPw iat-mode=0
n Bridge obfs4 207.181.244.13:40132 37FE8D782F5DD2BAEEDAAB8257B701344676B6DD cert=f5Hbfn3ToMzH170cV8DfLly3vRynveidfOfDcbradIDtbLDX15V2yQ8eEH2CPKQJmQR2Hg iat-mode=0
n Bridge obfs4 76.255.201.112:8888 96CF36C2ECCFB7376AB6BE905BECD2C2AE8AEFCD cert=+q0pjaiM0JMqHL/BKqCRD+pjflaw/S406eUDF7CnFgamvQW3l2HVLJhQ6uX9P8zff0PLGg iat-mode=0
n Bridge obfs4 65.108.159.114:14174 E1AD374BA9F34BD98862D128AC54D40C7DC138AE cert=YMkxMSBN2OOd99AkJpFaEUyKkdqZgJt9oVJEgO1QnT37n/Vc2yR4nhx4k4VkPLfEP1f4eg iat-mode=0
n Bridge obfs4 185.177.207.138:8443 53716FE26F23C8C6A71A2BC5D9D8DC64747278C7 cert=6jcYVilMEzxdsWghSrQFpYYJlkkir/GPIXw/EnddUK3S8ToVpMG8u1SwMOEdEs735RrMHw iat-mode=0
n Bridge obfs4 176.123.2.253:1933 B855D141CE6C4DE0F7EA4AAED83EBA8373FA8191 cert=1rOoSaRagc6PPR//paIl+ukv1N+xWKCdBXMFxK0k/moEwH0lk5bURBrUDzIX35fVzaiicQ iat-mode=0
n Bridge obfs4 5.252.176.61:9418 3E61130100AD827AB9CB33DAC052D9BC49A39509 cert=/aMyBPnKbQFISithD3i1KHUdpWeMpWG3SvUpq1YWCf2EQohFxQfw+646gW1knm4BI/DLRA iat-mode=0
n Bridge obfs4 202.61.224.111:6902 A4F91299763DB925AE3BD29A0FC1A9821E5D9BAE cert=NBKm2MJ83wMvYShkqpD5RrbDtW5YpIZrFNnMw7Dj1XOM3plU60Bh4eziaQXe8fGtb8ZqKg iat-mode=0
n Bridge obfs4 87.121.72.109:9002 C8081D4731C953FA4AE166946E72B29153351E34 cert=bikAqxKV6Ch5gFCBTdPI28VeShYa1ZgkLmvc7YZNLWFsFZoaCULL/3AQKjpIfvSiJs5jGQ iat-mode=0
n Bridge obfs4 172.104.17.96:17900 B6B37AC96E163D0A5ECE55826D17B50B70F0A7F8 cert=gUz7svhPxoALgeN4lMYrXK7NBnaDqwu6SKRJOhaO9IIMBpnB8UhMCMKzzMho3b0RxWzBVg iat-mode=0
n Bridge obfs4 70.34.249.113:443 F441B16ABB1055794C2CE01821FC05047B2C8CFC cert=MauLNoyq8EwjY4Qe0oASYzs2hXdSjNgy+BtP9oo1naHhRsyKTtAZzeNv08RnzWjMJrTwcg iat-mode=0
v Bridge 135.181.113.164:54444 74AF4CCA614C454B7D3E81FF8BACD78CEBC7D7DE
v Bridge 92.243.15.235:9001 477EAD3C04036B48235F1F27FC91420A286A4B7F
v Bridge 77.96.91.103:443 ED000A75B79A58F1D83A4D1675C2A9395B71BE8E
v Bridge 213.108.108.145:17674 A39C0FE47963B6E8CFE9815549864DE544935A31
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com
m Bridge meek_lite 192.0.2.2:2 97700DFE9F483596DDA6264C4D7DF7641E1E39CE url=https://meek.azureedge.net/ front=ajax.aspnetcdn.com

View File

@@ -17,6 +17,7 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
import org.briarproject.bramble.api.transport.KeyManager;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.junit.Before;
import org.junit.Test;
import java.util.Collection;
@@ -62,9 +63,13 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
private final long timestamp = System.currentTimeMillis();
private final boolean alice = new Random().nextBoolean();
private final ContactManagerImpl contactManager =
new ContactManagerImpl(db, keyManager, identityManager,
pendingContactFactory);
private ContactManagerImpl contactManager;
@Before
public void setUp() {
contactManager = new ContactManagerImpl(db, keyManager,
identityManager, pendingContactFactory);
}
@Test
public void testAddContact() throws Exception {

View File

@@ -2,6 +2,7 @@ package org.briarproject.bramble.data;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.util.StringUtils;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -16,8 +17,14 @@ import static org.junit.Assert.assertArrayEquals;
public class BdfWriterImplTest extends BrambleTestCase {
private final ByteArrayOutputStream out = new ByteArrayOutputStream();
private final BdfWriterImpl w = new BdfWriterImpl(out);
private ByteArrayOutputStream out = null;
private BdfWriterImpl w = null;
@Before
public void setUp() {
out = new ByteArrayOutputStream();
w = new BdfWriterImpl(out);
}
@Test
public void testWriteNull() throws IOException {

View File

@@ -303,11 +303,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(27).of(database).startTransaction();
exactly(25).of(database).startTransaction();
will(returnValue(txn));
exactly(27).of(database).containsContact(txn, contactId);
exactly(25).of(database).containsContact(txn, contactId);
will(returnValue(false));
exactly(27).of(database).abortTransaction(txn);
exactly(25).of(database).abortTransaction(txn);
}});
DatabaseComponent db = createDatabaseComponent(database, eventBus,
eventExecutor, shutdownManager);
@@ -321,23 +321,6 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
// Expected
}
try {
db.transaction(true, transaction ->
db.containsAcksToSend(transaction, contactId));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(true, transaction ->
db.containsMessagesToSend(transaction, contactId,
123, true));
fail();
} catch (NoSuchContactException expected) {
// Expected
}
try {
db.transaction(false, transaction ->
db.generateAck(transaction, contactId, 123));

View File

@@ -378,9 +378,9 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Initially there should be nothing to send
assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
// Add some messages to ack
Message message1 = getMessage(groupId);
@@ -389,7 +389,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message1, DELIVERED, true, false, contactId);
// Both message IDs should be returned
assertTrue(db.containsAcksToSend(txn, contactId));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids);
@@ -397,7 +400,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.lowerAckFlag(txn, contactId, asList(messageId, messageId1));
// No message IDs should be returned
assertFalse(db.containsAcksToSend(txn, contactId));
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertFalse(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
assertEquals(emptyList(), db.getMessagesToAck(txn, contactId, 1234));
// Raise the ack flag again
@@ -405,7 +411,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.raiseAckFlag(txn, contactId, messageId1);
// Both message IDs should be returned
assertTrue(db.containsAcksToSend(txn, contactId));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
assertTrue(
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
ids = db.getMessagesToAck(txn, contactId, 1234);
assertEquals(asList(messageId, messageId1), ids);
@@ -2020,51 +2029,37 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.addMessage(txn, message, UNKNOWN, false, false, null);
// There should be no messages to send
assertEquals(Long.MAX_VALUE,
db.getNextSendTime(txn, contactId, MAX_LATENCY));
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
// Share the group with the contact - still no messages to send
db.addGroupVisibility(txn, contactId, groupId, true);
assertEquals(Long.MAX_VALUE,
db.getNextSendTime(txn, contactId, MAX_LATENCY));
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
// Set the message's state to DELIVERED - still no messages to send
db.setMessageState(txn, messageId, DELIVERED);
assertEquals(Long.MAX_VALUE,
db.getNextSendTime(txn, contactId, MAX_LATENCY));
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
// Share the message - now it should be sendable immediately
db.setMessageShared(txn, messageId, true);
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
assertEquals(0, db.getNextSendTime(txn, contactId));
// Mark the message as requested - it should still be sendable
db.raiseRequestedFlag(txn, contactId, messageId);
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
assertEquals(0, db.getNextSendTime(txn, contactId));
// Update the message's expiry time as though we sent it - now the
// message should be sendable after one round-trip
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
assertEquals(now + MAX_LATENCY * 2,
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// The message should be sendable immediately over a transport with
// lower latency
assertEquals(0L, db.getNextSendTime(txn, contactId, MAX_LATENCY - 1));
db.updateRetransmissionData(txn, contactId, messageId, 1000);
assertEquals(now + 2000, db.getNextSendTime(txn, contactId));
// Update the message's expiry time again - now it should be sendable
// after two round-trips
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
assertEquals(now + MAX_LATENCY * 4,
db.getNextSendTime(txn, contactId, MAX_LATENCY));
// The message should be sendable immediately over a transport with
// lower latency
assertEquals(0L, db.getNextSendTime(txn, contactId, MAX_LATENCY - 1));
db.updateRetransmissionData(txn, contactId, messageId, 1000);
assertEquals(now + 4000, db.getNextSendTime(txn, contactId));
// Delete the message - there should be no messages to send
db.deleteMessage(txn, messageId);
assertEquals(Long.MAX_VALUE,
db.getNextSendTime(txn, contactId, MAX_LATENCY));
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
db.commitTransaction(txn);
db.close();
@@ -2129,8 +2124,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2,
db.getNextSendTime(txn, contactId, MAX_LATENCY));
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
// Time: now + MAX_LATENCY * 2 - 1
// The message should not yet be sendable
@@ -2173,8 +2167,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2,
db.getNextSendTime(txn, contactId, MAX_LATENCY));
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
// The message should not be sendable via the same transport
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
@@ -2221,8 +2214,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
db.updateRetransmissionData(txn, contactId, messageId, MAX_LATENCY);
// The message should expire after 2 * MAX_LATENCY
assertEquals(now + MAX_LATENCY * 2,
db.getNextSendTime(txn, contactId, MAX_LATENCY));
assertEquals(now + MAX_LATENCY * 2, db.getNextSendTime(txn, contactId));
// Time: now + MAX_LATENCY * 2 - 1
// The message should not yet be sendable
@@ -2233,8 +2225,8 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
// Reset the retransmission times
db.resetUnackedMessagesToSend(txn, contactId);
// The message should be sendable immediately
assertEquals(0, db.getNextSendTime(txn, contactId, MAX_LATENCY));
// The message should have infinitely short expiry
assertEquals(0, db.getNextSendTime(txn, contactId));
// The message should be sendable
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
@@ -2587,7 +2579,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertNothingToSendLazily(Database<Connection> db,
Connection txn) throws Exception {
assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertTrue(ids.isEmpty());
@@ -2598,7 +2590,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertOneMessageToSendLazily(Database<Connection> db,
Connection txn) throws Exception {
assertTrue(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, false));
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, false));
Collection<MessageId> ids =
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE, MAX_LATENCY);
assertEquals(singletonList(messageId), ids);
@@ -2609,7 +2601,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertNothingToSendEagerly(Database<Connection> db,
Connection txn) throws Exception {
assertFalse(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> unacked =
db.getUnackedMessagesToSend(txn, contactId);
assertTrue(unacked.isEmpty());
@@ -2619,7 +2611,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
private void assertOneMessageToSendEagerly(Database<Connection> db,
Connection txn) throws Exception {
assertTrue(
db.containsMessagesToSend(txn, contactId, MAX_LATENCY, true));
db.containsAnythingToSend(txn, contactId, MAX_LATENCY, true));
Collection<MessageId> unacked =
db.getUnackedMessagesToSend(txn, contactId);
assertEquals(singletonList(messageId), unacked);

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import static java.util.Collections.singletonList;
@@ -40,8 +41,13 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
private final KeyPair handshakeKeyPair =
new KeyPair(handshakePublicKey, handshakePrivateKey);
private final IdentityManagerImpl identityManager =
new IdentityManagerImpl(db, crypto, authorFactory, clock);
private IdentityManagerImpl identityManager;
@Before
public void setUp() {
identityManager =
new IdentityManagerImpl(db, crypto, authorFactory, clock);
}
@Test
public void testOpenDatabaseIdentityRegistered() throws Exception {

View File

@@ -10,6 +10,7 @@ import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -33,8 +34,12 @@ public class LifecycleManagerImplTest extends BrambleMockTestCase {
private final SecretKey dbKey = getSecretKey();
private final LifecycleManagerImpl lifecycleManager =
new LifecycleManagerImpl(db, eventBus, clock);
private LifecycleManagerImpl lifecycleManager;
@Before
public void setUp() {
lifecycleManager = new LifecycleManagerImpl(db, eventBus, clock);
}
@Test
public void testOpenDatabaseHooksAreCalledAtStartup() throws Exception {

View File

@@ -187,32 +187,6 @@ public class ConnectivityCheckerImplTest extends BrambleMockTestCase {
checker.onConnectivityCheckSucceeded(now);
}
@Test
public void testCheckIsCancelledWhenObserverIsRemoved() {
ConnectivityCheckerImpl checker = createChecker();
// When checkConnectivity() is called a check should be started
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(mailboxApiCaller).retryWithBackoff(apiCall);
will(returnValue(task));
}});
checker.checkConnectivity(properties, observer1);
// When the observer is removed the check should be cancelled
context.checking(new Expectations() {{
oneOf(task).cancel();
}});
checker.removeObserver(observer1);
// If the check runs anyway (cancellation came too late) the observer
// should not be called
checker.onConnectivityCheckSucceeded(now);
}
private ConnectivityCheckerImpl createChecker() {
return new ConnectivityCheckerImpl(clock, mailboxApiCaller) {

View File

@@ -1,131 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
public class ContactMailboxClientTest extends BrambleMockTestCase {
private final MailboxWorkerFactory workerFactory =
context.mock(MailboxWorkerFactory.class);
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
private final TorReachabilityMonitor reachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
private final MailboxWorker uploadWorker =
context.mock(MailboxWorker.class, "uploadWorker");
private final MailboxWorker downloadWorker =
context.mock(MailboxWorker.class, "downloadWorker");
private final MailboxProperties properties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxFolderId inboxId =
requireNonNull(properties.getInboxId());
private final MailboxFolderId outboxId =
requireNonNull(properties.getOutboxId());
private final ContactId contactId = getContactId();
private final ContactMailboxClient client =
new ContactMailboxClient(workerFactory, connectivityChecker,
reachabilityMonitor);
@Test
public void testStartAndDestroyWithNoContactsAssigned() {
client.start();
client.destroy();
}
@Test
public void testAssignContactForUploadAndDestroyClient() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartUploadWorker();
client.assignContactForUpload(contactId, properties, outboxId);
// When the client is destroyed, the worker should be destroyed
expectDestroyWorker(uploadWorker);
client.destroy();
}
@Test
public void testAssignAndDeassignContactForUpload() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartUploadWorker();
client.assignContactForUpload(contactId, properties, outboxId);
// When the contact is deassigned, the worker should be destroyed
expectDestroyWorker(uploadWorker);
client.deassignContactForUpload(contactId);
context.assertIsSatisfied();
client.destroy();
}
@Test
public void assignContactForDownloadAndDestroyClient() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartDownloadWorker();
client.assignContactForDownload(contactId, properties, inboxId);
// When the client is destroyed, the worker should be destroyed
expectDestroyWorker(downloadWorker);
client.destroy();
}
@Test
public void assignAndDeassignContactForDownload() {
client.start();
// When the contact is assigned, the worker should be created and
// started
expectCreateAndStartDownloadWorker();
client.assignContactForDownload(contactId, properties, inboxId);
// When the contact is deassigned, the worker should be destroyed
expectDestroyWorker(downloadWorker);
client.deassignContactForDownload(contactId);
context.assertIsSatisfied();
client.destroy();
}
private void expectCreateAndStartUploadWorker() {
context.checking(new Expectations() {{
oneOf(workerFactory).createUploadWorker(connectivityChecker,
properties, outboxId, contactId);
will(returnValue(uploadWorker));
oneOf(uploadWorker).start();
}});
}
private void expectCreateAndStartDownloadWorker() {
context.checking(new Expectations() {{
oneOf(workerFactory).createDownloadWorkerForContactMailbox(
connectivityChecker, reachabilityMonitor, properties);
will(returnValue(downloadWorker));
oneOf(downloadWorker).start();
}});
}
private void expectDestroyWorker(MailboxWorker worker) {
context.checking(new Expectations() {{
oneOf(worker).destroy();
}});
}
}

View File

@@ -1,239 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.mailbox.MailboxFileId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxFile;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertFalse;
public class ContactMailboxDownloadWorkerTest extends BrambleMockTestCase {
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
private final TorReachabilityMonitor torReachabilityMonitor =
context.mock(TorReachabilityMonitor.class);
private final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
private final MailboxFileManager mailboxFileManager =
context.mock(MailboxFileManager.class);
private final Cancellable apiCall = context.mock(Cancellable.class);
private final MailboxProperties mailboxProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
private final MailboxFile file1 =
new MailboxFile(new MailboxFileId(getRandomId()), now - 1);
private final MailboxFile file2 =
new MailboxFile(new MailboxFileId(getRandomId()), now);
private final List<MailboxFile> files = asList(file1, file2);
private File testDir, tempFile;
private ContactMailboxDownloadWorker worker;
@Before
public void setUp() {
testDir = getTestDirectory();
tempFile = new File(testDir, "temp");
worker = new ContactMailboxDownloadWorker(connectivityChecker,
torReachabilityMonitor, mailboxApiCaller, mailboxApi,
mailboxFileManager, mailboxProperties);
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
@Test
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
// When the worker is started it should start a connectivity check
context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
worker.start();
// When the worker is destroyed it should remove the connectivity
// and reachability observers
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(torReachabilityMonitor).removeObserver(worker);
}});
worker.destroy();
}
@Test
public void testDownloadsFilesWhenConnectivityCheckSucceeds()
throws Exception {
// When the worker is started it should start a connectivity check
context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
worker.start();
// When the connectivity check succeeds, a list-inbox task should be
// started for the first download cycle
AtomicReference<ApiCall> listTask = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(listTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
worker.onConnectivityCheckSucceeded();
// When the list-inbox tasks runs and finds some files to download,
// it should start a download task for the first file
AtomicReference<ApiCall> downloadTask = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(files));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(listTask.get().callApi());
// When the first download task runs it should download the file to the
// location provided by the file manager and start a delete task
AtomicReference<ApiCall> deleteTask = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createTempFileForDownload();
will(returnValue(tempFile));
oneOf(mailboxApi).getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file1.name, tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(downloadTask.get().callApi());
// When the first delete task runs it should delete the file, ignore
// the tolerable failure, and start a download task for the next file
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file1.name);
will(throwException(new TolerableFailureException()));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(downloadTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(deleteTask.get().callApi());
// When the second download task runs it should download the file to
// the location provided by the file manager and start a delete task
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createTempFileForDownload();
will(returnValue(tempFile));
oneOf(mailboxApi).getFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()),
file2.name, tempFile);
oneOf(mailboxFileManager).handleDownloadedFile(tempFile);
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(deleteTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(downloadTask.get().callApi());
// When the second delete task runs it should delete the file and
// start a list-inbox task to check for files that may have arrived
// since the first download cycle started
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteFile(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()), file2.name);
will(throwException(new TolerableFailureException()));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(listTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
assertFalse(deleteTask.get().callApi());
// When the list-inbox tasks runs and finds no more files to download,
// it should add a Tor reachability observer
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(emptyList()));
oneOf(torReachabilityMonitor).addOneShotObserver(worker);
}});
assertFalse(listTask.get().callApi());
// When the reachability observer is called, a list-inbox task should
// be started for the second download cycle
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(listTask, ApiCall.class, 0),
returnValue(apiCall)
));
}});
worker.onTorReachable();
// When the list-inbox tasks runs and finds no more files to download,
// it should finish the second download cycle
context.checking(new Expectations() {{
oneOf(mailboxApi).getFiles(mailboxProperties,
requireNonNull(mailboxProperties.getInboxId()));
will(returnValue(emptyList()));
}});
assertFalse(listTask.get().callApi());
// When the worker is destroyed it should remove the connectivity
// and reachability observers
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(torReachabilityMonitor).removeObserver(worker);
}});
worker.destroy();
}
}

View File

@@ -2,20 +2,16 @@ package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.connection.ConnectionManager;
import org.briarproject.bramble.api.connection.ConnectionManager.TagController;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState;
import org.briarproject.bramble.api.plugin.PluginManager;
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
import org.briarproject.bramble.api.plugin.event.TransportActiveEvent;
import org.briarproject.bramble.api.plugin.simplex.SimplexPlugin;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.ConsumeArgumentAction;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
@@ -24,7 +20,6 @@ import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
@@ -33,14 +28,11 @@ import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleS
import static org.briarproject.bramble.api.mailbox.MailboxConstants.ID;
import static org.briarproject.bramble.api.plugin.file.FileConstants.PROP_PATH;
import static org.briarproject.bramble.mailbox.MailboxFileManagerImpl.DOWNLOAD_DIR_NAME;
import static org.briarproject.bramble.mailbox.MailboxFileManagerImpl.UPLOAD_DIR_NAME;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class MailboxFileManagerImplTest extends BrambleMockTestCase {
@@ -55,10 +47,6 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
private final SimplexPlugin plugin = context.mock(SimplexPlugin.class);
private final TransportConnectionReader transportConnectionReader =
context.mock(TransportConnectionReader.class);
private final TransportConnectionWriter transportConnectionWriter =
context.mock(TransportConnectionWriter.class);
private final ContactId contactId = getContactId();
private File mailboxDir;
private MailboxFileManagerImpl manager;
@@ -77,25 +65,17 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
@Test
public void testHandlesOrphanedFilesAtStartup() throws Exception {
// Create an orphaned upload, left behind at the previous shutdown
File uploadDir = new File(mailboxDir, UPLOAD_DIR_NAME);
//noinspection ResultOfMethodCallIgnored
uploadDir.mkdirs();
File orphanedUpload = new File(uploadDir, "orphan");
assertTrue(orphanedUpload.createNewFile());
// Create an orphaned download, left behind at the previous shutdown
// Create an orphaned file, left behind at the previous shutdown
File downloadDir = new File(mailboxDir, DOWNLOAD_DIR_NAME);
//noinspection ResultOfMethodCallIgnored
downloadDir.mkdirs();
File orphanedDownload = new File(downloadDir, "orphan");
assertTrue(orphanedDownload.createNewFile());
File orphan = new File(downloadDir, "orphan");
assertTrue(orphan.createNewFile());
TransportProperties props = new TransportProperties();
props.put(PROP_PATH, orphanedDownload.getAbsolutePath());
props.put(PROP_PATH, orphan.getAbsolutePath());
// When the plugin becomes active the orphaned upload should be deleted
// and the orphaned download should be handled
// When the plugin becomes active the orphaned file should be handled
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
@@ -110,12 +90,10 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
}});
manager.eventOccurred(new TransportActiveEvent(ID));
assertFalse(orphanedUpload.exists());
}
@Test
public void testDeletesDownloadedFileWhenReadSucceeds() throws Exception {
public void testDeletesFileWhenReadSucceeds() throws Exception {
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
@@ -124,7 +102,7 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
new AtomicReference<>(null);
AtomicReference<TagController> controller = new AtomicReference<>(null);
expectPassDownloadedFileToConnectionManager(f, reader, controller);
expectPassFileToConnectionManager(f, reader, controller);
manager.handleDownloadedFile(f);
// The read is successful, so the tag controller should allow the tag
@@ -139,117 +117,29 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
}
@Test
public void testDeletesDownloadedFileWhenTagIsNotRecognised()
throws Exception {
testDeletesDownloadedFile(false, RUNNING, false);
public void testDeletesFileWhenTagIsNotRecognised() throws Exception {
testDeletesFile(false, RUNNING, false);
}
@Test
public void testDeletesDownloadedFileWhenReadFails() throws Exception {
testDeletesDownloadedFile(true, RUNNING, false);
public void testDeletesFileWhenReadFails() throws Exception {
testDeletesFile(true, RUNNING, false);
}
@Test
public void testDoesNotDeleteDownloadedFileWhenTagIsNotRecognisedAtShutdown()
public void testDoesNotDeleteFileWhenTagIsNotRecognisedAtShutdown()
throws Exception {
testDeletesDownloadedFile(false, STOPPING, true);
testDeletesFile(false, STOPPING, true);
}
@Test
public void testDoesNotDeleteDownloadedFileWhenReadFailsAtShutdown()
public void testDoesNotDeleteFileWhenReadFailsAtShutdown()
throws Exception {
testDeletesDownloadedFile(true, STOPPING, true);
testDeletesFile(true, STOPPING, true);
}
@Test(expected = IOException.class)
public void testThrowsExceptionIfPluginFailsToCreateWriter()
throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createWriter(with(any(TransportProperties.class)));
will(returnValue(null));
}});
manager.createAndWriteTempFileForUpload(contactId, sessionRecord);
}
@Test(expected = IOException.class)
public void testThrowsExceptionIfSessionFailsWithException()
throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createWriter(with(any(TransportProperties.class)));
will(returnValue(transportConnectionWriter));
oneOf(transportConnectionWriter).dispose(true);
oneOf(connectionManager).manageOutgoingConnection(with(contactId),
with(ID), with(any(TransportConnectionWriter.class)),
with(sessionRecord));
// The session fails with an exception. We need to use an action
// for this, as createAndWriteTempFileForUpload() waits for it to
// happen before returning
will(new ConsumeArgumentAction<>(TransportConnectionWriter.class, 2,
writer -> {
try {
writer.dispose(true);
} catch (IOException e) {
fail();
}
}
));
}});
manager.createAndWriteTempFileForUpload(contactId, sessionRecord);
}
@Test
public void testReturnsFileIfSessionSucceeds() throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
context.checking(new Expectations() {{
oneOf(pluginManager).getPlugin(ID);
will(returnValue(plugin));
oneOf(plugin).createWriter(with(any(TransportProperties.class)));
will(returnValue(transportConnectionWriter));
oneOf(transportConnectionWriter).dispose(false);
oneOf(connectionManager).manageOutgoingConnection(with(contactId),
with(ID), with(any(TransportConnectionWriter.class)),
with(sessionRecord));
// The session succeeds. We need to use an action for this, as
// createAndWriteTempFileForUpload() waits for it to happen before
// returning
will(new ConsumeArgumentAction<>(TransportConnectionWriter.class, 2,
writer -> {
try {
writer.dispose(false);
} catch (IOException e) {
fail();
}
}
));
}});
File f = manager.createAndWriteTempFileForUpload(contactId,
sessionRecord);
assertTrue(f.exists());
}
private void testDeletesDownloadedFile(boolean recognised,
LifecycleState state, boolean fileExists) throws Exception {
private void testDeletesFile(boolean recognised, LifecycleState state,
boolean fileExists) throws Exception {
expectCheckForOrphans();
manager.eventOccurred(new TransportActiveEvent(ID));
@@ -258,7 +148,7 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
new AtomicReference<>(null);
AtomicReference<TagController> controller = new AtomicReference<>(null);
expectPassDownloadedFileToConnectionManager(f, reader, controller);
expectPassFileToConnectionManager(f, reader, controller);
manager.handleDownloadedFile(f);
context.checking(new Expectations() {{
@@ -279,7 +169,7 @@ public class MailboxFileManagerImplTest extends BrambleMockTestCase {
}});
}
private void expectPassDownloadedFileToConnectionManager(File f,
private void expectPassFileToConnectionManager(File f,
AtomicReference<TransportConnectionReader> reader,
AtomicReference<TagController> controller) {
TransportProperties props = new TransportProperties();

View File

@@ -1,128 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.junit.Test;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.Executor;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.hasEvent;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class MailboxManagerImplTest extends BrambleMockTestCase {
private final Executor ioExecutor = context.mock(Executor.class);
private final MailboxApi api = context.mock(MailboxApi.class);
private final TransactionManager db =
context.mock(TransactionManager.class);
private final MailboxSettingsManager mailboxSettingsManager =
context.mock(MailboxSettingsManager.class);
private final MailboxPairingTaskFactory pairingTaskFactory =
context.mock(MailboxPairingTaskFactory.class);
private final Clock clock =
context.mock(Clock.class);
private final MailboxManagerImpl manager = new MailboxManagerImpl(
ioExecutor, api, db, mailboxSettingsManager, pairingTaskFactory,
clock);
@Test
public void testDbExceptionDoesNotRecordFailure() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(true),
withNullableDbCallable(txn));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(throwException(new DbException()));
}});
assertFalse(manager.checkConnection());
assertFalse(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
}
@Test
public void testIOExceptionDoesRecordFailure() throws Exception {
Transaction txn = new Transaction(null, true);
Transaction txn2 = new Transaction(null, false);
MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS);
long now = new Random().nextLong();
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(true),
withNullableDbCallable(txn));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(props));
oneOf(api).getServerSupports(props);
will(throwException(new IOException()));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn2));
oneOf(mailboxSettingsManager)
.recordFailedConnectionAttempt(txn2, now);
}});
assertFalse(manager.checkConnection());
}
@Test
public void testApiExceptionDoesRecordFailure() throws Exception {
Transaction txn = new Transaction(null, true);
Transaction txn2 = new Transaction(null, false);
MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS);
long now = new Random().nextLong();
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(true),
withNullableDbCallable(txn));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(props));
oneOf(api).getServerSupports(props);
will(throwException(new MailboxApi.ApiException()));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn2));
oneOf(mailboxSettingsManager)
.recordFailedConnectionAttempt(txn2, now);
}});
assertFalse(manager.checkConnection());
}
@Test
public void testConnectionSuccess() throws Exception {
Transaction txn = new Transaction(null, true);
Transaction txn2 = new Transaction(null, false);
MailboxProperties props = getMailboxProperties(true, CLIENT_SUPPORTS);
long now = new Random().nextLong();
context.checking(new DbExpectations() {{
oneOf(db).transactionWithNullableResult(with(true),
withNullableDbCallable(txn));
oneOf(mailboxSettingsManager).getOwnMailboxProperties(txn);
will(returnValue(props));
oneOf(api).getServerSupports(props);
will(returnValue(CLIENT_SUPPORTS));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn2));
oneOf(mailboxSettingsManager)
.recordSuccessfulConnection(txn2, now, CLIENT_SUPPORTS);
}});
assertTrue(manager.checkConnection());
}
}

View File

@@ -13,6 +13,7 @@ import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.mailbox.event.OwnMailboxConnectionStatusEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
@@ -32,6 +33,7 @@ import static java.util.Collections.singletonList;
import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.hasEvent;
import static org.briarproject.bramble.util.StringUtils.getRandomString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -136,6 +138,7 @@ public class MailboxPairingTaskImplTest extends BrambleMockTestCase {
i.getAndIncrement();
});
task.run();
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
}
@Test

View File

@@ -18,7 +18,6 @@ import java.util.List;
import java.util.Random;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
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_SUCCESS;
@@ -163,28 +162,6 @@ public class MailboxSettingsManagerImplTest extends BrambleMockTestCase {
}});
manager.recordSuccessfulConnection(txn, now);
assertTrue(hasEvent(txn, OwnMailboxConnectionStatusEvent.class));
}
@Test
public void testRecordsSuccessWithVersions() throws Exception {
Transaction txn = new Transaction(null, false);
List<MailboxVersion> versions = singletonList(new MailboxVersion(2, 1));
Settings expectedSettings = new Settings();
expectedSettings.putLong(SETTINGS_KEY_LAST_ATTEMPT, now);
expectedSettings.putLong(SETTINGS_KEY_LAST_SUCCESS, now);
expectedSettings.putInt(SETTINGS_KEY_ATTEMPTS, 0);
expectedSettings.putInt(SETTINGS_KEY_SERVER_SUPPORTS, 0);
int[] newVersionsInts = {2, 1};
expectedSettings
.putIntArray(SETTINGS_KEY_SERVER_SUPPORTS, newVersionsInts);
context.checking(new Expectations() {{
oneOf(settingsManager).mergeSettings(txn, expectedSettings,
SETTINGS_NAMESPACE);
}});
manager.recordSuccessfulConnection(txn, now, versions);
hasEvent(txn, OwnMailboxConnectionStatusEvent.class);
}

View File

@@ -1,465 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.mailbox.MailboxFolderId;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.TaskScheduler;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.ConsumeArgumentAction;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.MAX_LATENCY;
import static org.briarproject.bramble.mailbox.MailboxUploadWorker.CHECK_DELAY_MS;
import static org.briarproject.bramble.mailbox.MailboxUploadWorker.RETRY_DELAY_MS;
import static org.briarproject.bramble.test.TestUtils.deleteTestDirectory;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class MailboxUploadWorkerTest extends BrambleMockTestCase {
private final Executor ioExecutor = context.mock(Executor.class);
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final Clock clock = context.mock(Clock.class);
private final TaskScheduler taskScheduler =
context.mock(TaskScheduler.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
private final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
private final MailboxFileManager mailboxFileManager =
context.mock(MailboxFileManager.class);
private final Cancellable apiCall =
context.mock(Cancellable.class, "apiCall");
private final Cancellable wakeupTask =
context.mock(Cancellable.class, "wakeupTask");
private final Cancellable checkTask =
context.mock(Cancellable.class, "checkTask");
private final MailboxProperties mailboxProperties =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
private final MailboxFolderId folderId = new MailboxFolderId(getRandomId());
private final ContactId contactId = getContactId();
private final MessageId ackedId = new MessageId(getRandomId());
private final MessageId sentId = new MessageId(getRandomId());
private final MessageId newMessageId = new MessageId(getRandomId());
private File testDir, tempFile;
private MailboxUploadWorker worker;
@Before
public void setUp() {
testDir = getTestDirectory();
tempFile = new File(testDir, "temp");
worker = new MailboxUploadWorker(ioExecutor, db, clock, taskScheduler,
eventBus, connectivityChecker, mailboxApiCaller, mailboxApi,
mailboxFileManager, mailboxProperties, folderId, contactId);
}
@After
public void tearDown() {
deleteTestDirectory(testDir);
}
@Test
public void testChecksForDataWhenStartedAndRemovesObserverWhenDestroyed()
throws Exception {
// When the worker is started it should check for data to send
expectRunTaskOnIoExecutor();
expectCheckForDataToSendNoDataWaiting();
worker.start();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testChecksConnectivityWhenStartedIfDataIsReady()
throws Exception {
Transaction recordTxn = new Transaction(null, false);
// When the worker is started it should check for data to send. As
// there's data ready to send immediately, the worker should start a
// connectivity check
expectRunTaskOnIoExecutor();
expectCheckForDataToSendAndStartConnectivityCheck();
worker.start();
// Create the temporary file so we can test that it gets deleted
assertTrue(testDir.mkdirs());
assertTrue(tempFile.createNewFile());
// When the connectivity check succeeds, the worker should write a file
// and start an upload task
expectRunTaskOnIoExecutor();
AtomicReference<ApiCall> upload = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createAndWriteTempFileForUpload(
with(contactId), with(any(OutgoingSessionRecord.class)));
will(new DoAllAction(
// Record some IDs as acked and sent
new ConsumeArgumentAction<>(OutgoingSessionRecord.class, 1,
record -> {
record.onAckSent(singletonList(ackedId));
record.onMessageSent(sentId);
}),
returnValue(tempFile)
));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(upload, ApiCall.class, 0),
returnValue(apiCall)
));
}});
worker.onConnectivityCheckSucceeded();
// When the upload task runs, it should upload the file, record
// the acked/sent messages in the DB, and check for more data to send
context.checking(new DbExpectations() {{
oneOf(mailboxApi).addFile(mailboxProperties, folderId, tempFile);
oneOf(db).transaction(with(false), withDbRunnable(recordTxn));
oneOf(db).setAckSent(recordTxn, contactId, singletonList(ackedId));
oneOf(db).setMessagesSent(recordTxn, contactId,
singletonList(sentId), MAX_LATENCY);
}});
expectCheckForDataToSendNoDataWaiting();
assertFalse(upload.get().callApi());
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
// The file should have been deleted
assertFalse(tempFile.exists());
}
@Test
public void testCancelsApiCallWhenDestroyed() throws Exception {
// When the worker is started it should check for data to send. As
// there's data ready to send immediately, the worker should start a
// connectivity check
expectRunTaskOnIoExecutor();
expectCheckForDataToSendAndStartConnectivityCheck();
worker.start();
// Create the temporary file so we can test that it gets deleted
assertTrue(testDir.mkdirs());
assertTrue(tempFile.createNewFile());
// When the connectivity check succeeds, the worker should write a file
// and start an upload task
expectRunTaskOnIoExecutor();
AtomicReference<ApiCall> upload = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createAndWriteTempFileForUpload(
with(contactId), with(any(OutgoingSessionRecord.class)));
will(new DoAllAction(
// Record some IDs as acked and sent
new ConsumeArgumentAction<>(OutgoingSessionRecord.class, 1,
record -> {
record.onAckSent(singletonList(ackedId));
record.onMessageSent(sentId);
}),
returnValue(tempFile)
));
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(upload, ApiCall.class, 0),
returnValue(apiCall)
));
}});
worker.onConnectivityCheckSucceeded();
// When the worker is destroyed it should remove the connectivity
// observer and event listener and cancel the upload task
context.checking(new Expectations() {{
oneOf(apiCall).cancel();
}});
expectRemoveObserverAndListener();
worker.destroy();
// The file should have been deleted
assertFalse(tempFile.exists());
// If the upload task runs anyway (cancellation came too late), it
// should return early when it finds the state has changed
assertFalse(upload.get().callApi());
}
@Test
public void testSchedulesWakeupWhenStartedIfDataIsNotReady()
throws Exception {
// When the worker is started it should check for data to send. As
// the data isn't ready to send immediately, the worker should
// schedule a wakeup
expectRunTaskOnIoExecutor();
AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckForDataToSendAndScheduleWakeup(wakeup);
worker.start();
// When the wakeup task runs it should check for data to send
expectCheckForDataToSendNoDataWaiting();
wakeup.get().run();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testCancelsWakeupIfDestroyedBeforeWakingUp() throws Exception {
// When the worker is started it should check for data to send. As
// the data isn't ready to send immediately, the worker should
// schedule a wakeup
expectRunTaskOnIoExecutor();
AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckForDataToSendAndScheduleWakeup(wakeup);
worker.start();
// When the worker is destroyed it should cancel the wakeup and
// remove the connectivity observer and event listener
context.checking(new Expectations() {{
oneOf(wakeupTask).cancel();
}});
expectRemoveObserverAndListener();
worker.destroy();
// If the wakeup task runs anyway (cancellation came too late), it
// should return early without doing anything
wakeup.get().run();
}
@Test
public void testCancelsWakeupIfEventIsReceivedBeforeWakingUp()
throws Exception {
// When the worker is started it should check for data to send. As
// the data isn't ready to send immediately, the worker should
// schedule a wakeup
expectRunTaskOnIoExecutor();
AtomicReference<Runnable> wakeup = new AtomicReference<>();
expectCheckForDataToSendAndScheduleWakeup(wakeup);
worker.start();
// Before the wakeup task runs, the worker receives an event that
// indicates new data may be available. The worker should cancel the
// wakeup task and schedule a check for new data after a short delay
AtomicReference<Runnable> check = new AtomicReference<>();
expectScheduleCheck(check, CHECK_DELAY_MS);
context.checking(new Expectations() {{
oneOf(wakeupTask).cancel();
}});
worker.eventOccurred(new MessageSharedEvent(newMessageId));
// If the wakeup task runs anyway (cancellation came too late), it
// should return early when it finds the state has changed
wakeup.get().run();
// Before the check task runs, the worker receives another event that
// indicates new data may be available. The event should be ignored,
// as a check for new data has already been scheduled
worker.eventOccurred(new MessageSharedEvent(newMessageId));
// When the check task runs, it should check for new data
expectCheckForDataToSendNoDataWaiting();
check.get().run();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
@Test
public void testCancelsCheckWhenDestroyed() throws Exception {
// When the worker is started it should check for data to send
expectRunTaskOnIoExecutor();
expectCheckForDataToSendNoDataWaiting();
worker.start();
// The worker receives an event that indicates new data may be
// available. The worker should schedule a check for new data after
// a short delay
AtomicReference<Runnable> check = new AtomicReference<>();
expectScheduleCheck(check, CHECK_DELAY_MS);
worker.eventOccurred(new MessageSharedEvent(newMessageId));
// When the worker is destroyed it should cancel the check and
// remove the connectivity observer and event listener
context.checking(new Expectations() {{
oneOf(checkTask).cancel();
}});
expectRemoveObserverAndListener();
worker.destroy();
// If the check runs anyway (cancellation came too late), it should
// return early when it finds the state has changed
check.get().run();
}
@Test
public void testRetriesAfterDelayIfExceptionOccursWhileWritingFile()
throws Exception {
// When the worker is started it should check for data to send. As
// there's data ready to send immediately, the worker should start a
// connectivity check
expectRunTaskOnIoExecutor();
expectCheckForDataToSendAndStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should try to
// write a file. This fails with an exception, so the worker should
// retry by scheduling a check for new data after a short delay
expectRunTaskOnIoExecutor();
AtomicReference<Runnable> check = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(mailboxFileManager).createAndWriteTempFileForUpload(
with(contactId), with(any(OutgoingSessionRecord.class)));
will(throwException(new IOException())); // Oh noes!
}});
expectScheduleCheck(check, RETRY_DELAY_MS);
worker.onConnectivityCheckSucceeded();
// When the check task runs it should check for new data
expectCheckForDataToSendNoDataWaiting();
check.get().run();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveObserverAndListener();
worker.destroy();
}
private void expectRunTaskOnIoExecutor() {
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
}});
}
private void expectCheckForDataToSendNoDataWaiting() throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(db).containsAcksToSend(txn, contactId);
will(returnValue(false));
oneOf(db).getNextSendTime(txn, contactId, MAX_LATENCY);
will(returnValue(Long.MAX_VALUE)); // No data waiting
}});
}
private void expectCheckForDataToSendAndScheduleWakeup(
AtomicReference<Runnable> wakeup) throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(db).containsAcksToSend(txn, contactId);
will(returnValue(false));
oneOf(db).getNextSendTime(txn, contactId, MAX_LATENCY);
will(returnValue(now + 1234L)); // Data waiting but not ready
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(1234L), with(MILLISECONDS));
will(new DoAllAction(
new CaptureArgumentAction<>(wakeup, Runnable.class, 0),
returnValue(wakeupTask)
));
}});
}
private void expectCheckForDataToSendAndStartConnectivityCheck()
throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(db).containsAcksToSend(txn, contactId);
will(returnValue(false));
oneOf(db).getNextSendTime(txn, contactId, MAX_LATENCY);
will(returnValue(0L)); // Data ready to send
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(connectivityChecker).checkConnectivity(mailboxProperties,
worker);
}});
}
private void expectScheduleCheck(AtomicReference<Runnable> check,
long delay) {
context.checking(new Expectations() {{
oneOf(taskScheduler).schedule(with(any(Runnable.class)),
with(ioExecutor), with(delay), with(MILLISECONDS));
will(new DoAllAction(
new CaptureArgumentAction<>(check, Runnable.class, 0),
returnValue(checkTask)
));
}});
}
private void expectRemoveObserverAndListener() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(eventBus).removeListener(worker);
}});
}
}

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.db.TransactionManager;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxSettingsManager;
import org.briarproject.bramble.api.mailbox.MailboxVersion;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.mailbox.ConnectivityChecker.ConnectivityObserver;
import org.briarproject.bramble.test.BrambleMockTestCase;
@@ -16,10 +15,8 @@ import org.jmock.lib.action.DoAllAction;
import org.junit.Test;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.junit.Assert.assertFalse;
@@ -42,8 +39,6 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
private final MailboxProperties properties =
getMailboxProperties(true, CLIENT_SUPPORTS);
private final long now = System.currentTimeMillis();
private final List<MailboxVersion> serverSupports =
singletonList(new MailboxVersion(123, 456));
@Test
public void testObserverIsCalledWhenCheckSucceeds() throws Exception {
@@ -67,13 +62,12 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
// When the check succeeds, the success should be recorded in the DB
// and the observer should be called
context.checking(new DbExpectations() {{
oneOf(mailboxApi).getServerSupports(properties);
will(returnValue(serverSupports));
oneOf(mailboxApi).checkStatus(properties);
will(returnValue(true));
oneOf(clock).currentTimeMillis();
will(returnValue(now));
oneOf(db).transaction(with(false), withDbRunnable(txn));
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now,
serverSupports);
oneOf(mailboxSettingsManager).recordSuccessfulConnection(txn, now);
oneOf(observer).onConnectivityCheckSucceeded();
}});
@@ -103,7 +97,7 @@ public class OwnMailboxConnectivityCheckerTest extends BrambleMockTestCase {
// When the check fails, the failure should be recorded in the DB and
// the observer should not be called
context.checking(new DbExpectations() {{
oneOf(mailboxApi).getServerSupports(properties);
oneOf(mailboxApi).checkStatus(properties);
will(throwException(new IOException()));
oneOf(clock).currentTimeMillis();
will(returnValue(now));

View File

@@ -1,406 +0,0 @@
package org.briarproject.bramble.mailbox;
import org.briarproject.bramble.api.Cancellable;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.contact.event.ContactAddedEvent;
import org.briarproject.bramble.api.contact.event.ContactRemovedEvent;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.NoSuchContactException;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.mailbox.MailboxProperties;
import org.briarproject.bramble.api.mailbox.MailboxUpdate;
import org.briarproject.bramble.api.mailbox.MailboxUpdateManager;
import org.briarproject.bramble.api.mailbox.MailboxUpdateWithMailbox;
import org.briarproject.bramble.mailbox.MailboxApi.MailboxContact;
import org.briarproject.bramble.mailbox.MailboxApi.TolerableFailureException;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.api.mailbox.MailboxConstants.CLIENT_SUPPORTS;
import static org.briarproject.bramble.api.nullsafety.NullSafety.requireNonNull;
import static org.briarproject.bramble.test.TestUtils.getContact;
import static org.briarproject.bramble.test.TestUtils.getMailboxProperties;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class OwnMailboxContactListWorkerTest extends BrambleMockTestCase {
private final Executor ioExecutor = context.mock(Executor.class);
private final DatabaseComponent db = context.mock(DatabaseComponent.class);
private final EventBus eventBus = context.mock(EventBus.class);
private final ConnectivityChecker connectivityChecker =
context.mock(ConnectivityChecker.class);
private final MailboxApiCaller mailboxApiCaller =
context.mock(MailboxApiCaller.class);
private final MailboxApi mailboxApi = context.mock(MailboxApi.class);
private final MailboxUpdateManager mailboxUpdateManager =
context.mock(MailboxUpdateManager.class);
private final Cancellable apiCall = context.mock(Cancellable.class);
private final MailboxProperties mailboxProperties =
getMailboxProperties(true, CLIENT_SUPPORTS);
private final Contact contact1 = getContact(), contact2 = getContact();
private final MailboxProperties contactProperties1 =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxProperties contactProperties2 =
getMailboxProperties(false, CLIENT_SUPPORTS);
private final MailboxUpdateWithMailbox update1 =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, contactProperties1);
private final MailboxUpdateWithMailbox update2 =
new MailboxUpdateWithMailbox(CLIENT_SUPPORTS, contactProperties2);
private final OwnMailboxContactListWorker worker =
new OwnMailboxContactListWorker(ioExecutor, db, eventBus,
connectivityChecker, mailboxApiCaller, mailboxApi,
mailboxUpdateManager, mailboxProperties);
@Test
public void testChecksConnectivityWhenStartedAndRemovesObserverWhenDestroyed() {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
@Test
public void testUpdatesContactListWhenConnectivityCheckSucceeds()
throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should start a
// task to fetch the remote contact list
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
expectStartTaskToFetchRemoteContactList(fetchList);
worker.onConnectivityCheckSucceeded();
// When the fetch task runs it should fetch the remote contact list,
// load the local contact list, and find the differences. Contact 2
// needs to be added and contact 1 needs to be removed. The worker
// should load the mailbox update for contact 2 and start a task to
// add contact 2 to the mailbox
expectFetchRemoteContactList(singletonList(contact1.getId()));
expectRunTaskOnIoExecutor();
expectLoadLocalContactList(singletonList(contact2));
expectRunTaskOnIoExecutor();
expectLoadMailboxUpdate(contact2, update2);
AtomicReference<ApiCall> addContact = new AtomicReference<>();
expectStartTaskToAddContact(addContact);
assertFalse(fetchList.get().callApi());
// When the add-contact task runs it should add contact 2 to the
// mailbox, then continue with the next update
AtomicReference<MailboxContact> added = new AtomicReference<>();
expectAddContactToMailbox(added);
AtomicReference<ApiCall> removeContact = new AtomicReference<>();
expectStartTaskToRemoveContact(removeContact);
assertFalse(addContact.get().callApi());
// Check that the added contact has the expected properties
MailboxContact expected = new MailboxContact(contact2.getId(),
contactProperties2.getAuthToken(),
requireNonNull(contactProperties2.getInboxId()),
requireNonNull(contactProperties2.getOutboxId()));
assertMailboxContactEquals(expected, added.get());
// When the remove-contact task runs it should remove contact 1 from
// the mailbox
expectRemoveContactFromMailbox(contact1);
assertFalse(removeContact.get().callApi());
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
@Test
public void testHandlesEventsAfterMakingInitialUpdates() throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should start a
// task to fetch the remote contact list
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
expectStartTaskToFetchRemoteContactList(fetchList);
worker.onConnectivityCheckSucceeded();
// When the fetch task runs it should fetch the remote contact list,
// load the local contact list, and find the differences. The lists
// are the same, so the worker should wait for changes
expectFetchRemoteContactList(emptyList());
expectRunTaskOnIoExecutor();
expectLoadLocalContactList(emptyList());
assertFalse(fetchList.get().callApi());
// When a contact is added, the worker should load the contact's
// mailbox update and start a task to add the contact to the mailbox
expectRunTaskOnIoExecutor();
expectLoadMailboxUpdate(contact1, update1);
AtomicReference<ApiCall> addContact = new AtomicReference<>();
expectStartTaskToAddContact(addContact);
worker.eventOccurred(new ContactAddedEvent(contact1.getId(), true));
// When the add-contact task runs it should add contact 1 to the
// mailbox
AtomicReference<MailboxContact> added = new AtomicReference<>();
expectAddContactToMailbox(added);
assertFalse(addContact.get().callApi());
// Check that the added contact has the expected properties
MailboxContact expected = new MailboxContact(contact1.getId(),
contactProperties1.getAuthToken(),
requireNonNull(contactProperties1.getInboxId()),
requireNonNull(contactProperties1.getOutboxId()));
assertMailboxContactEquals(expected, added.get());
// When the contact is removed again, the worker should start a task
// to remove the contact from the mailbox
expectRunTaskOnIoExecutor();
AtomicReference<ApiCall> removeContact = new AtomicReference<>();
expectStartTaskToRemoveContact(removeContact);
worker.eventOccurred(new ContactRemovedEvent(contact1.getId()));
// When the remove-contact task runs it should remove the contact from
// the mailbox
expectRemoveContactFromMailbox(contact1);
assertFalse(removeContact.get().callApi());
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
@Test
public void testHandlesNoSuchContactException() throws Exception {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should start a
// task to fetch the remote contact list
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
expectStartTaskToFetchRemoteContactList(fetchList);
worker.onConnectivityCheckSucceeded();
// When the fetch task runs it should fetch the remote contact list,
// load the local contact list, and find the differences. Contact 1
// needs to be added, so the worker should submit a task to the
// IO executor to load the contact's mailbox update
expectFetchRemoteContactList(emptyList());
expectRunTaskOnIoExecutor();
expectLoadLocalContactList(singletonList(contact1));
AtomicReference<Runnable> loadUpdate = new AtomicReference<>();
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new CaptureArgumentAction<>(loadUpdate, Runnable.class, 0));
}});
assertFalse(fetchList.get().callApi());
// Before the contact's mailbox update can be loaded, the contact
// is removed
worker.eventOccurred(new ContactRemovedEvent(contact1.getId()));
// When the load-update task runs, a NoSuchContactException is thrown.
// The worker should abandon adding the contact and move on to the
// next update, which is the removal of the same contact. The worker
// should start a task to remove the contact from the mailbox
Transaction txn = new Transaction(null, false);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true), withDbCallable(txn));
oneOf(mailboxUpdateManager).getLocalUpdate(txn, contact1.getId());
will(throwException(new NoSuchContactException()));
}});
AtomicReference<ApiCall> removeContact = new AtomicReference<>();
expectStartTaskToRemoveContact(removeContact);
loadUpdate.get().run();
// When the remove-contact task runs it should remove the contact from
// the mailbox. The contact was never added, so the call throws a
// TolerableFailureException
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteContact(mailboxProperties,
contact1.getId());
will(throwException(new TolerableFailureException()));
}});
assertFalse(removeContact.get().callApi());
// When the worker is destroyed it should remove the connectivity
// observer and event listener
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
@Test
public void testCancelsApiCallWhenDestroyed() {
// When the worker is started it should start a connectivity check
expectStartConnectivityCheck();
worker.start();
// When the connectivity check succeeds, the worker should start a
// task to fetch the remote contact list
AtomicReference<ApiCall> fetchList = new AtomicReference<>();
expectStartTaskToFetchRemoteContactList(fetchList);
worker.onConnectivityCheckSucceeded();
// The worker is destroyed before the task runs. The worker should
// cancel the task remove the connectivity observer and event listener
context.checking(new Expectations() {{
oneOf(apiCall).cancel();
}});
expectRemoveConnectivityObserverAndEventListener();
worker.destroy();
}
private void expectStartConnectivityCheck() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).checkConnectivity(
with(mailboxProperties), with(worker));
}});
}
private void expectRemoveConnectivityObserverAndEventListener() {
context.checking(new Expectations() {{
oneOf(connectivityChecker).removeObserver(worker);
oneOf(eventBus).removeListener(worker);
}});
}
private void expectStartTaskToFetchRemoteContactList(
AtomicReference<ApiCall> task) {
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(task, ApiCall.class, 0),
returnValue(apiCall)
));
}});
}
private void expectFetchRemoteContactList(List<ContactId> remote)
throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).getContacts(mailboxProperties);
will(returnValue(remote));
}});
}
private void expectLoadLocalContactList(List<Contact> local)
throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transaction(with(true), withDbRunnable(txn));
oneOf(db).getContacts(txn);
will(returnValue(local));
}});
}
private void expectLoadMailboxUpdate(Contact c, MailboxUpdate update)
throws Exception {
Transaction txn = new Transaction(null, true);
context.checking(new DbExpectations() {{
oneOf(db).transactionWithResult(with(true),
withDbCallable(txn));
oneOf(mailboxUpdateManager).getLocalUpdate(txn, c.getId());
will(returnValue(update));
}});
}
private void expectStartTaskToAddContact(AtomicReference<ApiCall> task) {
context.checking(new Expectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(task, ApiCall.class, 0),
returnValue(apiCall)
));
}});
}
private void expectAddContactToMailbox(
AtomicReference<MailboxContact> added) throws Exception {
context.checking(new DbExpectations() {{
oneOf(mailboxApi).addContact(with(mailboxProperties),
with(any(MailboxContact.class)));
will(new CaptureArgumentAction<>(added, MailboxContact.class, 1));
}});
}
private void expectStartTaskToRemoveContact(AtomicReference<ApiCall> task) {
context.checking(new DbExpectations() {{
oneOf(mailboxApiCaller).retryWithBackoff(with(any(ApiCall.class)));
will(new DoAllAction(
new CaptureArgumentAction<>(task, ApiCall.class, 0),
returnValue(apiCall)
));
}});
}
private void expectRemoveContactFromMailbox(Contact c) throws Exception {
context.checking(new Expectations() {{
oneOf(mailboxApi).deleteContact(mailboxProperties, c.getId());
}});
}
private void expectRunTaskOnIoExecutor() {
context.checking(new Expectations() {{
oneOf(ioExecutor).execute(with(any(Runnable.class)));
will(new RunAction());
}});
}
private void assertMailboxContactEquals(MailboxContact expected,
MailboxContact actual) {
assertEquals(expected.contactId, actual.contactId);
assertEquals(expected.token, actual.token);
assertEquals(expected.inboxId, actual.inboxId);
assertEquals(expected.outboxId, actual.outboxId);
}
}

View File

@@ -12,6 +12,7 @@ import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.CaptureArgumentAction;
import org.jmock.Expectations;
import org.jmock.lib.action.DoAllAction;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.Executor;
@@ -37,9 +38,13 @@ public class TorReachabilityMonitorImplTest extends BrambleMockTestCase {
private final TorReachabilityObserver observer =
context.mock(TorReachabilityObserver.class);
private final TorReachabilityMonitorImpl monitor =
new TorReachabilityMonitorImpl(ioExecutor, taskScheduler,
pluginManager, eventBus);
private TorReachabilityMonitorImpl monitor;
@Before
public void setUp() {
monitor = new TorReachabilityMonitorImpl(ioExecutor, taskScheduler,
pluginManager, eventBus);
}
@Test
public void testSchedulesTaskWhenStartedIfTorIsActive() {

View File

@@ -26,6 +26,7 @@ import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.RunAction;
import org.jmock.Expectations;
import org.jmock.imposters.ByteBuddyClassImposteriser;
import org.junit.Before;
import org.junit.Test;
import java.security.SecureRandom;
@@ -58,18 +59,22 @@ public class PollerImplTest extends BrambleMockTestCase {
private final SecureRandom random;
private final Executor ioExecutor = new ImmediateExecutor();
private final Executor wakefulIoExecutor = new ImmediateExecutor();
private final TransportId transportId = getTransportId();
private final ContactId contactId = getContactId();
private final TransportProperties properties = new TransportProperties();
private final int pollingInterval = 60 * 1000;
private final long now = System.currentTimeMillis();
private final PollerImpl poller;
private PollerImpl poller;
public PollerImplTest() {
context.setImposteriser(ByteBuddyClassImposteriser.INSTANCE);
random = context.mock(SecureRandom.class);
Executor wakefulIoExecutor = new ImmediateExecutor();
}
@Before
public void setUp() {
poller = new PollerImpl(ioExecutor, wakefulIoExecutor, scheduler,
connectionManager, connectionRegistry, pluginManager,
transportPropertyManager, random, clock);

View File

@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.api.settings.Settings;
import org.briarproject.bramble.test.BrambleTestCase;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
@@ -45,11 +46,13 @@ public class LanTcpPluginTest extends BrambleTestCase {
private final Backoff backoff = new TestBackoff();
private final ExecutorService ioExecutor = newCachedThreadPool();
private final Callback callback = new Callback();
private final LanTcpPlugin plugin;
private Callback callback = null;
private LanTcpPlugin plugin = null;
public LanTcpPluginTest() {
@Before
public void setUp() {
callback = new Callback();
plugin = new LanTcpPlugin(ioExecutor, ioExecutor, backoff, callback,
0, 0, 1000) {
@Override

View File

@@ -7,6 +7,7 @@ import java.util.HashSet;
import java.util.Set;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BLOCKED;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.DEFAULT_OBFS4;
@@ -14,7 +15,7 @@ import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeTy
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.NON_DEFAULT_OBFS4;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.BridgeType.VANILLA;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DEFAULT_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.DPI_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.MEEK_BRIDGES;
import static org.briarproject.bramble.plugin.tor.CircumventionProvider.NON_DEFAULT_BRIDGES;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -31,18 +32,18 @@ public class CircumventionProviderTest extends BrambleTestCase {
Set<String> defaultBridges = new HashSet<>(asList(DEFAULT_BRIDGES));
Set<String> nonDefaultBridges =
new HashSet<>(asList(NON_DEFAULT_BRIDGES));
Set<String> dpiBridges = new HashSet<>(asList(DPI_BRIDGES));
Set<String> meekBridges = new HashSet<>(asList(MEEK_BRIDGES));
// BRIDGES should be a subset of BLOCKED
assertTrue(blocked.containsAll(bridges));
// BRIDGES should be the union of the bridge type sets
Set<String> union = new HashSet<>(defaultBridges);
union.addAll(nonDefaultBridges);
union.addAll(dpiBridges);
union.addAll(meekBridges);
assertEquals(bridges, union);
// The bridge type sets should not overlap
assertEmptyIntersection(defaultBridges, nonDefaultBridges);
assertEmptyIntersection(defaultBridges, dpiBridges);
assertEmptyIntersection(nonDefaultBridges, dpiBridges);
assertEmptyIntersection(defaultBridges, meekBridges);
assertEmptyIntersection(nonDefaultBridges, meekBridges);
}
@Test
@@ -55,8 +56,8 @@ public class CircumventionProviderTest extends BrambleTestCase {
assertEquals(asList(NON_DEFAULT_OBFS4, VANILLA),
provider.getSuitableBridgeTypes(country));
}
for (String country : DPI_BRIDGES) {
assertEquals(asList(NON_DEFAULT_OBFS4, MEEK),
for (String country : MEEK_BRIDGES) {
assertEquals(singletonList(MEEK),
provider.getSuitableBridgeTypes(country));
}
assertEquals(asList(DEFAULT_OBFS4, VANILLA),

View File

@@ -34,6 +34,7 @@ import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.briarproject.bramble.test.PredicateMatcher;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.util.Random;
@@ -92,10 +93,14 @@ public class RendezvousPollerImplTest extends BrambleMockTestCase {
getTransportProperties(3);
private final boolean alice = new Random().nextBoolean();
private final RendezvousPollerImpl rendezvousPoller =
new RendezvousPollerImpl(ioExecutor, scheduler, db,
identityManager, transportCrypto, rendezvousCrypto,
pluginManager, connectionManager, eventBus, clock);
private RendezvousPollerImpl rendezvousPoller;
@Before
public void setUp() {
rendezvousPoller = new RendezvousPollerImpl(ioExecutor, scheduler, db,
identityManager, transportCrypto, rendezvousCrypto,
pluginManager, connectionManager, eventBus, clock);
}
@Test
public void testAddsPendingContactsAndSchedulesPollingAtStartup()

View File

@@ -6,10 +6,10 @@ import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.DeferredSendHandler;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.OutgoingSessionRecord;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.api.transport.StreamWriter;
@@ -30,7 +30,6 @@ import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
import static org.junit.Assert.assertEquals;
public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
@@ -41,6 +40,8 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
private final StreamWriter streamWriter = context.mock(StreamWriter.class);
private final SyncRecordWriter recordWriter =
context.mock(SyncRecordWriter.class);
private final DeferredSendHandler deferredSendHandler =
context.mock(DeferredSendHandler.class);
private final ContactId contactId = getContactId();
private final TransportId transportId = getTransportId();
@@ -52,10 +53,9 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
@Test
public void testNothingToSend() throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter, sessionRecord,
streamWriter, recordWriter, deferredSendHandler,
MAX_FILE_PAYLOAD_BYTES);
Transaction noAckIdTxn = new Transaction(null, true);
@@ -92,17 +92,13 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
}});
session.run();
assertEquals(emptyList(), sessionRecord.getAckedIds());
assertEquals(emptyList(), sessionRecord.getSentIds());
}
@Test
public void testSomethingToSend() throws Exception {
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter, sessionRecord,
streamWriter, recordWriter, deferredSendHandler,
MAX_FILE_PAYLOAD_BYTES);
Transaction ackIdTxn = new Transaction(null, true);
@@ -131,6 +127,8 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes));
oneOf(recordWriter).writeAck(with(any(Ack.class)));
oneOf(deferredSendHandler)
.onAckSent(singletonList(message.getId()));
// No more messages to ack
oneOf(db).transactionWithResult(with(true),
withDbCallable(noAckIdTxn));
@@ -152,6 +150,7 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
MAX_LATENCY, false);
will(returnValue(message1));
oneOf(recordWriter).writeMessage(message1);
oneOf(deferredSendHandler).onMessageSent(message1.getId());
// Send the end of stream marker
oneOf(streamWriter).sendEndOfStream();
// Remove listener
@@ -159,11 +158,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
}});
session.run();
assertEquals(singletonList(message.getId()),
sessionRecord.getAckedIds());
assertEquals(singletonList(message1.getId()),
sessionRecord.getSentIds());
}
@Test
@@ -173,10 +167,9 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
long capacity = RECORD_HEADER_BYTES + MessageId.LENGTH * MAX_MESSAGE_IDS
+ RECORD_HEADER_BYTES + MessageId.LENGTH + MessageId.LENGTH - 1;
OutgoingSessionRecord sessionRecord = new OutgoingSessionRecord();
MailboxOutgoingSession session = new MailboxOutgoingSession(db,
eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter, sessionRecord, capacity);
streamWriter, recordWriter, deferredSendHandler, capacity);
Transaction ackIdTxn1 = new Transaction(null, true);
Transaction ackIdTxn2 = new Transaction(null, true);
@@ -191,9 +184,6 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
}
List<MessageId> idsInSecondAck =
singletonList(new MessageId(getRandomId()));
List<MessageId> allIds = new ArrayList<>(MAX_MESSAGE_IDS + 1);
allIds.addAll(idsInFirstAck);
allIds.addAll(idsInSecondAck);
context.checking(new DbExpectations() {{
// Add listener
@@ -210,6 +200,7 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
will(returnValue(idsInFirstAck));
// Send the first ack record
oneOf(recordWriter).writeAck(with(any(Ack.class)));
oneOf(deferredSendHandler).onAckSent(idsInFirstAck);
// Calculate remaining capacity for acks
oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes + firstAckRecordBytes));
@@ -220,6 +211,7 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
will(returnValue(idsInSecondAck));
// Send the second ack record
oneOf(recordWriter).writeAck(with(any(Ack.class)));
oneOf(deferredSendHandler).onAckSent(idsInSecondAck);
// Not enough capacity left for another ack
oneOf(recordWriter).getBytesWritten();
will(returnValue((long) versionRecordBytes + firstAckRecordBytes
@@ -235,8 +227,5 @@ public class MailboxOutgoingSessionTest extends BrambleMockTestCase {
}});
session.run();
assertEquals(allIds, sessionRecord.getAckedIds());
assertEquals(emptyList(), sessionRecord.getSentIds());
}
}

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
@@ -43,8 +44,12 @@ public class SyncRecordReaderImplTest extends BrambleMockTestCase {
context.mock(MessageFactory.class);
private final RecordReader recordReader = context.mock(RecordReader.class);
private final SyncRecordReader reader =
new SyncRecordReaderImpl(messageFactory, recordReader);
private SyncRecordReader reader;
@Before
public void setUp() {
reader = new SyncRecordReaderImpl(messageFactory, recordReader);
}
@Test
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {

View File

@@ -20,6 +20,7 @@ import org.briarproject.bramble.api.sync.validation.MessageValidator;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.DbExpectations;
import org.briarproject.bramble.test.ImmediateExecutor;
import org.junit.Before;
import org.junit.Test;
import java.util.LinkedHashMap;
@@ -69,10 +70,11 @@ public class ValidationManagerImplTest extends BrambleMockTestCase {
private final MessageContext validResultWithDependencies =
new MessageContext(metadata, singletonList(messageId1));
private final ValidationManagerImpl vm =
new ValidationManagerImpl(db, dbExecutor, validationExecutor);
private ValidationManagerImpl vm;
public ValidationManagerImplTest() {
@Before
public void setUp() {
vm = new ValidationManagerImpl(db, dbExecutor, validationExecutor);
vm.registerMessageValidator(clientId, majorVersion, validator);
vm.registerIncomingMessageHook(clientId, majorVersion, hook);
}

View File

@@ -1,32 +0,0 @@
package org.briarproject.bramble.test;
import org.briarproject.bramble.api.Consumer;
import org.hamcrest.Description;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
public class ConsumeArgumentAction<T> implements Action {
private final Class<T> capturedClass;
private final int index;
private final Consumer<T> consumer;
public ConsumeArgumentAction(Class<T> capturedClass, int index,
Consumer<T> consumer) {
this.capturedClass = capturedClass;
this.index = index;
this.consumer = consumer;
}
@Override
public Object invoke(Invocation invocation) {
consumer.accept(capturedClass.cast(invocation.getParameter(index)));
return null;
}
@Override
public void describeTo(Description description) {
description.appendText("passes an argument to a consumer");
}
}

View File

@@ -22,6 +22,7 @@ import org.hamcrest.Description;
import org.jmock.Expectations;
import org.jmock.api.Action;
import org.jmock.api.Invocation;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
@@ -71,9 +72,13 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
private final SecretKey rootKey = getSecretKey();
private final Random random = new Random();
private final TransportKeyManager transportKeyManager =
new TransportKeyManagerImpl(db, transportCrypto, dbExecutor,
scheduler, clock, transportId, maxLatency);
private TransportKeyManager transportKeyManager;
@Before
public void setUp() {
transportKeyManager = new TransportKeyManagerImpl(db, transportCrypto,
dbExecutor, scheduler, clock, transportId, maxLatency);
}
@Test
public void testKeysAreUpdatedAtStartup() throws Exception {

View File

@@ -18,9 +18,7 @@ dependencies {
implementation "net.java.dev.jna:jna:$jna_version"
implementation "net.java.dev.jna:jna-platform:$jna_version"
tor "org.briarproject:tor-linux:$tor_version"
tor "org.briarproject:tor-windows:$tor_version"
tor "org.briarproject:obfs4proxy-linux:$obfs4proxy_version"
tor "org.briarproject:obfs4proxy-windows:$obfs4proxy_version"
annotationProcessor "com.google.dagger:dagger-compiler:$dagger_version"

View File

@@ -1,97 +0,0 @@
package org.briarproject.bramble.plugin.tor;
import com.sun.jna.platform.win32.Kernel32;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.PluginException;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import java.io.File;
import java.util.Scanner;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
@NotNullByDefault
class WindowsTorPlugin extends JavaTorPlugin {
WindowsTorPlugin(Executor ioExecutor,
Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
SocketFactory torSocketFactory,
Clock clock,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto,
PluginCallback callback,
String architecture,
long maxLatency,
int maxIdleTime,
File torDirectory,
int torSocksPort,
int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
torSocketFactory, clock, resourceProvider,
circumventionProvider, batteryManager, backoff,
torRendezvousCrypto, callback, architecture,
maxLatency, maxIdleTime, torDirectory, torSocksPort,
torControlPort);
}
@Override
protected int getProcessId() {
return Kernel32.INSTANCE.GetCurrentProcessId();
}
@Override
protected void waitForTorToStart(Process torProcess)
throws InterruptedException, PluginException {
// On Windows the RunAsDaemon option has no effect, so Tor won't detach.
// Wait for the control port to be opened, then continue to read its
// stdout and stderr in a background thread until it exits.
BlockingQueue<Boolean> success = new ArrayBlockingQueue<>(1);
ioExecutor.execute(() -> {
boolean started = false;
// Read the process's stdout (and redirected stderr)
Scanner stdout = new Scanner(torProcess.getInputStream());
// Log the first line of stdout (contains Tor and library versions)
if (stdout.hasNextLine()) LOG.info(stdout.nextLine());
// Startup has succeeded when the control port is open
while (stdout.hasNextLine()) {
String line = stdout.nextLine();
if (!started && line.contains("Opened Control listener")) {
success.add(true);
started = true;
}
}
stdout.close();
// If the control port wasn't opened, startup has failed
if (!started) success.add(false);
// Wait for the process to exit
try {
int exit = torProcess.waitFor();
if (LOG.isLoggable(INFO))
LOG.info("Tor exited with value " + exit);
} catch (InterruptedException e1) {
LOG.warning("Interrupted while waiting for Tor to exit");
Thread.currentThread().interrupt();
}
});
// Wait for the startup result
if (!success.take()) throw new PluginException();
}
}

View File

@@ -1,80 +0,0 @@
package org.briarproject.bramble.plugin.tor;
import org.briarproject.bramble.api.battery.BatteryManager;
import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.network.NetworkManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TorControlPort;
import org.briarproject.bramble.api.plugin.TorDirectory;
import org.briarproject.bramble.api.plugin.TorSocksPort;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.LocationUtils;
import org.briarproject.bramble.api.system.ResourceProvider;
import org.briarproject.bramble.api.system.WakefulIoExecutor;
import java.io.File;
import java.util.concurrent.Executor;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
import javax.inject.Inject;
import javax.net.SocketFactory;
import static java.util.logging.Level.INFO;
import static org.briarproject.bramble.util.OsUtils.isWindows;
@Immutable
@NotNullByDefault
public class WindowsTorPluginFactory extends TorPluginFactory {
@Inject
WindowsTorPluginFactory(@IoExecutor Executor ioExecutor,
@WakefulIoExecutor Executor wakefulIoExecutor,
NetworkManager networkManager,
LocationUtils locationUtils,
EventBus eventBus,
SocketFactory torSocketFactory,
BackoffFactory backoffFactory,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager,
Clock clock,
CryptoComponent crypto,
@TorDirectory File torDirectory,
@TorSocksPort int torSocksPort,
@TorControlPort int torControlPort) {
super(ioExecutor, wakefulIoExecutor, networkManager, locationUtils,
eventBus, torSocketFactory, backoffFactory, resourceProvider,
circumventionProvider, batteryManager, clock, crypto,
torDirectory, torSocksPort, torControlPort);
}
@Nullable
@Override
String getArchitectureForTorBinary() {
if (!isWindows()) return null;
String arch = System.getProperty("os.arch");
if (LOG.isLoggable(INFO)) {
LOG.info("System's os.arch is " + arch);
}
if (arch.equals("amd64")) return "windows-x86_64";
return null;
}
@Override
TorPlugin createPluginInstance(Backoff backoff,
TorRendezvousCrypto torRendezvousCrypto, PluginCallback callback,
String architecture) {
return new WindowsTorPlugin(ioExecutor, wakefulIoExecutor,
networkManager, locationUtils, torSocketFactory, clock,
resourceProvider, circumventionProvider, batteryManager,
backoff, torRendezvousCrypto, callback, architecture,
MAX_LATENCY, MAX_IDLE_TIME, torDirectory, torSocksPort,
torControlPort);
}
}

View File

@@ -16,7 +16,9 @@ public class DesktopSecureRandomModule {
@Provides
@Singleton
SecureRandomProvider provideSecureRandomProvider() {
if (isLinux() || isMac()) return new UnixSecureRandomProvider();
return () -> null; // Use system default
if (isLinux() || isMac())
return new UnixSecureRandomProvider();
// TODO: Create a secure random provider for Windows
throw new UnsupportedOperationException();
}
}

View File

@@ -4,6 +4,7 @@ import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.properties.TransportProperties;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
@@ -24,8 +25,12 @@ public class ModemPluginTest extends BrambleMockTestCase {
private final PluginCallback callback = context.mock(PluginCallback.class);
private final Modem modem = context.mock(Modem.class);
private final ModemPlugin plugin =
new ModemPlugin(modemFactory, serialPortList, callback, 0);
private ModemPlugin plugin;
@Before
public void setUp() {
plugin = new ModemPlugin(modemFactory, serialPortList, callback, 0);
}
@Test
public void testModemCreation() throws Exception {

View File

@@ -25,9 +25,7 @@ dependencyVerification {
'net.ltgt.gradle.incap:incap:0.2:incap-0.2.jar:b625b9806b0f1e4bc7a2e3457119488de3cd57ea20feedd513db070a573a4ffd',
'org.apache-extras.beanshell:bsh:2.0b6:bsh-2.0b6.jar:a17955976070c0573235ee662f2794a78082758b61accffce8d3f8aedcd91047',
'org.briarproject:obfs4proxy-linux:0.0.12:obfs4proxy-linux-0.0.12.jar:3dd83aff25fe1cb3e4eab78a02c76ac921f552be6877b3af83a472438525df2a',
'org.briarproject:obfs4proxy-windows:0.0.12:obfs4proxy-windows-0.0.12.jar:392aa4b9d9c6fef0c659c4068d019d6c6471991bbb62ff00553884ec36018c7b',
'org.briarproject:tor-linux:0.4.5.12-2:tor-linux-0.4.5.12-2.jar:d275f323faf5e70b33d2c8a1bdab1bb3ab5a0d8f4e23c4a6dda03d86f4e95838',
'org.briarproject:tor-windows:0.4.5.12-2:tor-windows-0.4.5.12-2.jar:46599a15d099ed35a360113293f66acc119571c24ec2e37e85e4fb54b4722e07',
'org.checkerframework:checker-compat-qual:2.5.3:checker-compat-qual-2.5.3.jar:d76b9afea61c7c082908023f0cbc1427fab9abd2df915c8b8a3e7a509bccbc6d',
'org.checkerframework:checker-qual:2.5.2:checker-qual-2.5.2.jar:64b02691c8b9d4e7700f8ee2e742dce7ea2c6e81e662b7522c9ee3bf568c040a',
'org.codehaus.mojo:animal-sniffer-annotations:1.17:animal-sniffer-annotations-1.17.jar:92654f493ecfec52082e76354f0ebf87648dc3d5cec2e3c3cdb947c016747a53',

View File

@@ -26,8 +26,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 30
versionCode 10409
versionName "1.4.9"
versionCode 10408
versionName "1.4.8"
applicationId "org.briarproject.briar.android"
vectorDrawables.useSupportLibrary = true
@@ -100,7 +100,7 @@ dependencies {
implementation project(path: ':briar-core', configuration: 'default')
implementation project(path: ':bramble-core', configuration: 'default')
implementation project(':bramble-android')
implementation 'org.briarproject:dont-kill-me-lib:0.2.5'
implementation 'org.briarproject:dont-kill-me-lib:0.2.2'
implementation 'androidx.fragment:fragment:1.3.4'
implementation 'androidx.preference:preference:1.1.1'

View File

@@ -493,6 +493,9 @@
<queries>
<package android:name="info.guardianproject.ripple" />
<package android:name="com.huawei.systemmanager" />
<package android:name="com.huawei.powergenie" />
<package android:name="com.evenwell.PowerMonitor" />
<intent>
<action android:name="android.intent.action.VIEW" />

View File

@@ -32,8 +32,7 @@ public class DozeFragment extends SetupFragment
private DozeView dozeView;
private HuaweiProtectedAppsView huaweiProtectedAppsView;
private HuaweiAppLaunchView huaweiAppLaunchView;
private XiaomiRecentAppsView xiaomiRecentAppsView;
private XiaomiLockAppsView xiaomiLockAppsView;
private XiaomiView xiaomiView;
private Button next;
private boolean secondAttempt = false;
@@ -55,10 +54,8 @@ public class DozeFragment extends SetupFragment
huaweiProtectedAppsView.setOnCheckedChangedListener(this);
huaweiAppLaunchView = v.findViewById(R.id.huaweiAppLaunchView);
huaweiAppLaunchView.setOnCheckedChangedListener(this);
xiaomiRecentAppsView = v.findViewById(R.id.xiaomiRecentAppsView);
xiaomiRecentAppsView.setOnCheckedChangedListener(this);
xiaomiLockAppsView = v.findViewById(R.id.xiaomiLockAppsView);
xiaomiLockAppsView.setOnCheckedChangedListener(this);
xiaomiView = v.findViewById(R.id.xiaomiView);
xiaomiView.setOnCheckedChangedListener(this);
next = v.findViewById(R.id.next);
ProgressBar progressBar = v.findViewById(R.id.progress);
@@ -105,8 +102,7 @@ public class DozeFragment extends SetupFragment
next.setEnabled(dozeView.isChecked() &&
huaweiProtectedAppsView.isChecked() &&
huaweiAppLaunchView.isChecked() &&
xiaomiRecentAppsView.isChecked() &&
xiaomiLockAppsView.isChecked());
xiaomiView.isChecked());
}
@SuppressLint("BatteryLife")

View File

@@ -50,7 +50,7 @@ class DozeView extends PowerView {
onButtonClickListener.run();
}
void setOnButtonClickListener(Runnable runnable) {
public void setOnButtonClickListener(Runnable runnable) {
onButtonClickListener = runnable;
}

View File

@@ -1,72 +0,0 @@
package org.briarproject.briar.android.account;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.Toast;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import java.util.logging.Logger;
import javax.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import static android.widget.Toast.LENGTH_LONG;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.android.dontkillmelib.XiaomiUtils.getXiaomiLockAppsIntent;
import static org.briarproject.android.dontkillmelib.XiaomiUtils.xiaomiLockAppsNeedsToBeShown;
import static org.briarproject.bramble.util.LogUtils.logException;
@UiThread
@NotNullByDefault
class XiaomiLockAppsView extends PowerView {
private static final Logger LOG =
getLogger(XiaomiLockAppsView.class.getName());
public XiaomiLockAppsView(Context context) {
this(context, null);
}
public XiaomiLockAppsView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public XiaomiLockAppsView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
setText(R.string.dnkm_xiaomi_lock_apps_text);
setButtonText(R.string.dnkm_xiaomi_lock_apps_button);
}
@Override
public boolean needsToBeShown() {
return xiaomiLockAppsNeedsToBeShown(getContext());
}
@Override
@StringRes
protected int getHelpText() {
return R.string.dnkm_xiaomi_lock_apps_help;
}
@Override
protected void onButtonClick() {
try {
getContext().startActivity(getXiaomiLockAppsIntent());
setChecked(true);
return;
} catch (SecurityException e) {
logException(LOG, WARNING, e);
}
Toast.makeText(getContext(),
R.string.dnkm_xiaomi_lock_apps_error_toast,
LENGTH_LONG).show();
// Let the user continue with setup
setChecked(true);
}
}

View File

@@ -1,5 +1,6 @@
package org.briarproject.briar.android.account;
import android.content.Context;
import android.util.AttributeSet;
@@ -11,23 +12,23 @@ import javax.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import static org.briarproject.android.dontkillmelib.XiaomiUtils.isMiuiVersionAtLeast;
import static org.briarproject.android.dontkillmelib.XiaomiUtils.xiaomiRecentAppsNeedsToBeShown;
import static org.briarproject.android.dontkillmelib.XiaomiUtils.isMiuiTenOrLater;
import static org.briarproject.android.dontkillmelib.XiaomiUtils.isXiaomiOrRedmiDevice;
import static org.briarproject.briar.android.util.UiUtils.showOnboardingDialog;
@UiThread
@NotNullByDefault
class XiaomiRecentAppsView extends PowerView {
class XiaomiView extends PowerView {
public XiaomiRecentAppsView(Context context) {
public XiaomiView(Context context) {
this(context, null);
}
public XiaomiRecentAppsView(Context context, @Nullable AttributeSet attrs) {
public XiaomiView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public XiaomiRecentAppsView(Context context, @Nullable AttributeSet attrs,
public XiaomiView(Context context, @Nullable AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
setText(R.string.dnkm_xiaomi_text);
@@ -36,7 +37,7 @@ class XiaomiRecentAppsView extends PowerView {
@Override
public boolean needsToBeShown() {
return xiaomiRecentAppsNeedsToBeShown();
return isXiaomiOrRedmiDevice();
}
@Override
@@ -47,7 +48,7 @@ class XiaomiRecentAppsView extends PowerView {
@Override
protected void onButtonClick() {
int bodyRes = isMiuiVersionAtLeast(10, 0)
int bodyRes = isMiuiTenOrLater()
? R.string.dnkm_xiaomi_dialog_body_new
: R.string.dnkm_xiaomi_dialog_body_old;
showOnboardingDialog(getContext(), getContext().getString(bodyRes));

View File

@@ -25,7 +25,6 @@ import javax.annotation.Nullable;
import javax.inject.Inject;
import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.Toolbar;
@@ -105,7 +104,7 @@ public abstract class BriarActivity extends BaseActivity {
LOG.info("Not signed in, launching StartupActivity");
Intent i = new Intent(this, StartupActivity.class);
startActivityForResult(i, REQUEST_PASSWORD);
} else if (SDK_INT >= 21 && lockManager.isLocked() && !isFinishing()) {
} else if (lockManager.isLocked() && !isFinishing()) {
// Also check that the activity isn't finishing already.
// This is possible if we finished in onActivityResult().
// Launching another UnlockActivity would cause a loop.
@@ -116,7 +115,10 @@ public abstract class BriarActivity extends BaseActivity {
briarController.hasDozed(new UiResultHandler<Boolean>(this) {
@Override
public void onResultUi(Boolean result) {
if (result) showDozeDialog(R.string.dnkm_warning_dozed_1);
if (result) {
showDozeDialog(getString(R.string.dnkm_warning_dozed,
getString(R.string.app_name)));
}
}
});
}
@@ -173,7 +175,7 @@ public abstract class BriarActivity extends BaseActivity {
return toolbar;
}
protected void showDozeDialog(@StringRes int message) {
protected void showDozeDialog(String message) {
AlertDialog.Builder b =
new AlertDialog.Builder(this, R.style.BriarDialogTheme);
b.setMessage(message);

View File

@@ -175,8 +175,13 @@ public class AddNearbyContactActivity extends BriarActivity
showErrorFragment();
} else {
String msg;
if (qrCodeTooOld) msg = getString(R.string.qr_code_too_old_1);
else msg = getString(R.string.qr_code_too_new_1);
if (qrCodeTooOld) {
msg = getString(R.string.qr_code_too_old,
getString(R.string.app_name));
} else {
msg = getString(R.string.qr_code_too_new,
getString(R.string.app_name));
}
showNextFragment(AddNearbyContactErrorFragment.newInstance(msg));
}
}

View File

@@ -78,15 +78,16 @@ class WebServer extends NanoHTTPD {
try (InputStream is = ctx.getAssets().open(FILE_HTML)) {
doc = Jsoup.parse(is, UTF_8.name(), "");
}
String app = ctx.getString(R.string.app_name);
String appV = app + " " + VERSION_NAME;
String filename = getApkFileName();
doc.select("#download_title").first()
.text(ctx.getString(R.string.website_download_title_1,
VERSION_NAME));
.text(ctx.getString(R.string.website_download_title, appV));
doc.select("#download_intro").first()
.text(ctx.getString(R.string.website_download_intro_1));
.text(ctx.getString(R.string.website_download_intro, app));
doc.select(".button").first().attr("href", filename);
doc.select("#download_button").first()
.text(ctx.getString(R.string.website_download_button));
.text(ctx.getString(R.string.website_download_title, app));
doc.select("#download_outro").first()
.text(ctx.getString(R.string.website_download_outro));
doc.select("#troubleshooting_title").first()

View File

@@ -63,8 +63,6 @@ public class MailboxStatusFragment extends Fragment {
private Button wizardButton;
private Button unlinkButton;
private ProgressBar unlinkProgress;
@Nullable
private AlertDialog dialog = null;
@Override
public void onAttach(Context context) {
@@ -131,15 +129,6 @@ public class MailboxStatusFragment extends Fragment {
refresher = null;
}
@Override
public void onDetach() {
super.onDetach();
if (dialog != null) {
dialog.dismiss();
dialog = null;
}
}
private void onMailboxStateChanged(MailboxStatus status) {
@ColorRes int tintRes;
@DrawableRes int iconRes;
@@ -227,15 +216,12 @@ public class MailboxStatusFragment extends Fragment {
(dialog, which) -> dialog.cancel());
builder.setNegativeButton(R.string.mailbox_status_unlink_button,
(dialog, which) -> {
ViewGroup v = (ViewGroup) getView();
if (v != null) beginDelayedTransition(v);
beginDelayedTransition((ViewGroup) requireView());
unlinkButton.setVisibility(INVISIBLE);
unlinkProgress.setVisibility(VISIBLE);
viewModel.unlink();
});
builder.setOnDismissListener(dialog ->
MailboxStatusFragment.this.dialog = null);
dialog = builder.show();
builder.show();
}
}

View File

@@ -135,23 +135,12 @@ class MailboxViewModel extends DbViewModel
} else if (e instanceof TransportInactiveEvent) {
TransportId id = ((TransportInactiveEvent) e).getTransportId();
if (!TorConstants.ID.equals(id)) return;
onTorInactive();
}
}
@UiThread
private void onTorInactive() {
MailboxState lastState = pairingState.getLastValue();
if (lastState instanceof MailboxState.IsPaired) {
// we are already paired, so use IsPaired state
pairingState.setEvent(new MailboxState.IsPaired(false));
} else if (lastState instanceof MailboxState.Pairing) {
MailboxState.Pairing p = (MailboxState.Pairing) lastState;
// check that we not just finished pairing (showing success screen)
if (!(p.pairingState instanceof MailboxPairingState.Paired)) {
MailboxState lastState = pairingState.getLastValue();
if (lastState instanceof MailboxState.IsPaired) {
pairingState.setEvent(new MailboxState.IsPaired(false));
} else if (lastState != null) {
pairingState.setEvent(new MailboxState.OfflineWhenPairing());
}
// else ignore offline event as user will be leaving UI flow anyway
}
}

View File

@@ -147,7 +147,7 @@ public class NavDrawerActivity extends BriarActivity implements
}
}
navDrawerViewModel.shouldAskForDozeWhitelisting().observe(this, ask -> {
if (ask) showDozeDialog(R.string.dnkm_doze_intro);
if (ask) showDozeDialog(getString(R.string.dnkm_doze_intro));
});
Toolbar toolbar = setUpCustomToolbar(false);

View File

@@ -38,8 +38,8 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/huaweiProtectedAppsView" />
<org.briarproject.briar.android.account.XiaomiRecentAppsView
android:id="@+id/xiaomiRecentAppsView"
<org.briarproject.briar.android.account.XiaomiView
android:id="@+id/xiaomiView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_large"
@@ -47,15 +47,6 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/huaweiAppLaunchView" />
<org.briarproject.briar.android.account.XiaomiLockAppsView
android:id="@+id/xiaomiLockAppsView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="@dimen/margin_large"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/xiaomiRecentAppsView" />
<Button
android:id="@+id/next"
style="@style/BriarButton"
@@ -66,7 +57,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/xiaomiLockAppsView"
app:layout_constraintTop_toBottomOf="@+id/xiaomiView"
app:layout_constraintVertical_bias="1.0"
tools:enabled="true" />

View File

@@ -18,7 +18,7 @@
<string name="more_info">Повече информация</string>
<string name="don_t_ask_again">Спиране на този въпрос</string>
<string name="dnkm_huawei_protected_text">Докоснете бутона по-долу и се уверете, че Briar е защитен в екрана за „Защитени приложения“.</string>
<string name="dnkm_huawei_protected_button">Предпазване на Briar</string>
<string name="dnkm_huawei_protected_button">Защитаване на Briar</string>
<string name="dnkm_huawei_protected_help">Ако не добавите Briar в списъка на защитени приложения, няма да може да работи на заден план.</string>
<string name="dnkm_huawei_app_launch_text">Докоснете бутона по-долу, отворете „Стартиране на приложения“ и се уверете, че за Briar е избрано „Ръчно управление“.</string>
<string name="dnkm_huawei_app_launch_help">Ако за Briar не е избрано „Ръчно управление“ в екрана „Стартиране на приложения“, тогава няма да може да работи на заден план.</string>
@@ -26,7 +26,7 @@
<string name="dnkm_xiaomi_button">Предпазване на Briar</string>
<string name="dnkm_xiaomi_help">Ако Briar не е заключен в списъка с последно използваните приложения, няма да работи на заден план.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Отворете списъка с отворени приложения (списък за превключване на приложения)\n\n2. Плъзнете надолу върху изображението на Briar докато се покаже икона на катинар\n\n3. Ако катинарът е отключен го докоснете, за да го заключите</string>
<string name="dnkm_warning_dozed_1">Briar не може да работи във фонов режим</string>
<string name="dnkm_xiaomi_dialog_body_new">1. Отворете списъка с отворени приложения (списък за превключване на приложения)\n\n2. Докоснете и задръжте върху изображението на Briar докато се покаже икона на катинар\n\n3. Ако катинарът е отключен го докоснете, за да го заключите</string>
<!--Login-->
<string name="enter_password">Парола</string>
<string name="try_again">Грешна парола, опитайте отново</string>
@@ -48,7 +48,7 @@
</plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 не се поддържа. Briar ще спре да работи на %s (след %d ден). Инсталирайте Briar на друго устройство и създайте нов профил.</item>
<item quantity="other">Android 4 не се поддържа. Briar ще спре да работи на %s (след %d дена). Инсталирайте Briar на друго устройство и създайте нов профил.</item>
<item quantity="other">Android 4 не се поддържа. Briar ще спре да работи на %s (след %d дни). Инсталирайте Briar на друго устройство и създайте нов профил.</item>
</plurals>
<string name="expiry_date_reached">Софтуерът е с изтекъл срок.\nБлагодарим за изпитването!</string>
<string name="download_briar">За да продължите да използвате Briar изтеглете последното издание.</string>
@@ -87,8 +87,8 @@
<!--Transports: Wi-Fi-->
<string name="transport_lan">Wi-Fi</string>
<string name="transport_lan_long">Същата безжична мрежа</string>
<string name="lan_device_status_on">Устройството е свързано с безжична мрежа</string>
<string name="lan_device_status_off">Устройството не е свързано с безжична мрежа</string>
<string name="lan_device_status_on">Устройството е свързан с безжична мрежа</string>
<string name="lan_device_status_off">Устройството не е свързан с безжична мрежа</string>
<string name="lan_plugin_status_enabling">Briar се свързва с безжична мрежа</string>
<string name="lan_plugin_status_active">Briar е свързан с безжична мрежа</string>
<string name="lan_plugin_status_inactive">Briar не е свързан с безжична мрежа</string>
@@ -230,8 +230,8 @@
<string name="contact_added_toast">Добавен контакт: %s</string>
<string name="contact_already_exists">Контактът %s вече съществува</string>
<string name="qr_code_invalid">Кодът за QR е недействителен</string>
<string name="qr_code_too_old_1">Сканираният код за QR е от по-ранно издание на Briar.\n\nНека вашия контакт инсталира последното издание и да пробва отново.</string>
<string name="qr_code_too_new_1">Сканираният код за QR е от по-ново издание на Briar.\n\nИнсталирайте последното издание и пробвайте отново.</string>
<string name="qr_code_too_old">Сканираният код за QR е от по-ранно издание на %s.\n\nНека вашия контакт инсталира последното издание и да пробва отново.</string>
<string name="qr_code_too_new">Сканираният код за QR е от по-ново издание на %s.\n\nИнсталирайте последното издание и пробвайте отново.</string>
<string name="camera_error">Грешка в камерата</string>
<string name="connecting_to_device">Свързване с устройство\u2026</string>
<string name="authenticating_with_device">Удостоверяване с устройство\u2026</string>
@@ -584,34 +584,25 @@
<string name="mailbox_setup_qr_code_wrong_title">Грешен код на QR</string>
<string name="mailbox_setup_qr_code_wrong_description">Сканираният код е недействителен. Отворете приложението Briar Malibox на устройството, на което е инсталирано и сканирайте кода, който то предостави.</string>
<string name="mailbox_setup_already_paired_title">Пощенската кутия е вече свързана</string>
<string name="mailbox_setup_already_paired_description">Прекъснете връзката с пощенската кутия от другото устройство и опитайте отново.</string>
<string name="mailbox_setup_already_paired_description">Изключете връзката с пощенската кутия на другото устройство и опитайте отново.</string>
<string name="mailbox_setup_io_error_title">Грешка при свързване</string>
<string name="mailbox_setup_io_error_description">Уверете се, че и двете устройства са свързани към интернет, и опитайте отново.</string>
<string name="mailbox_setup_assertion_error_title">Грешка в пощенската кутия</string>
<string name="mailbox_setup_assertion_error_description">Изпратете обратна връзка (с анонимизирани данни) през приложението Briar ако проблемът продължи да се появява.</string>
<string name="mailbox_setup_camera_error_description">Няма достъп до камерата. Опитайте отново и след рестарт на усройството.</string>
<string name="mailbox_setup_paired_title">Свързан</string>
<string name="mailbox_setup_paired_description">Пощенската кутия е свързана с Briar.\n\nЗа да е винаги на линия, я дръжте включена в захранване и свързана с безжична мрежа.</string>
<string name="tor_offline_title">Извън линия</string>
<string name="tor_offline_button_check">Проверете настройките на връзката</string>
<string name="mailbox_status_title">Състояние на пощенаската кутия</string>
<string name="mailbox_status_connected_title">Пощенската кутия работи</string>
<string name="mailbox_status_problem_title">Briar не може да се свърже с пощенската кутия</string>
<string name="mailbox_status_failure_title">Пощенската кутия не е достъпна</string>
<string name="mailbox_status_app_too_old_title">Изданието на Briar е твърде старо</string>
<string name="mailbox_status_app_too_old_message">Инсталирайте последното издание на Briar и пробвайте отново.</string>
<string name="mailbox_status_mailbox_too_old_title">Изданието на Пощенската кутия е твърде старо</string>
<string name="mailbox_status_mailbox_too_old_message">Инсталирайте последното издание на Пощенска кутия и пробвайте отново.</string>
<string name="mailbox_status_check_button">Проверка на връзката</string>
<!--Example for string substitution: Last connection: 3min ago-->
<string name="mailbox_status_connected_info">Последно свързване: %s</string>
<!--Indicates that there never was a connection to the mailbox. Last connection: Never-->
<string name="mailbox_status_connected_never">Никога</string>
<string name="mailbox_status_unlink_button">Прекъсване на връзка</string>
<string name="mailbox_status_unlink_dialog_title">Ще прекъснете ли връзката с пощенската кутия?</string>
<string name="mailbox_status_unlink_dialog_question">Сигурни ли сте, че желаете да прекъснете връзката с пощенската кутия?</string>
<string name="mailbox_status_unlink_dialog_warning">Ако прекъснете връзката с пощенската кутия, няма да получавате съобщения докато Briar е без мрежа.</string>
<string name="mailbox_status_unlink_no_wipe_title">Връзката с пощенската кутия е прекъсната</string>
<string name="mailbox_status_unlink_button">Изключване на връзка</string>
<string name="mailbox_status_unlink_dialog_title">Ще изключите ли връзката с пощенската кутия?</string>
<string name="mailbox_status_unlink_dialog_question">Сигурни ли сте, че желаете да изключите връзката с пощенската кутия?</string>
<string name="mailbox_error_wizard_info2">Върнете се на този екран, когато получите достъп до устройството.</string>
<!--Conversation Settings-->
<string name="disappearing_messages_title">Изчезващи съобщения</string>
@@ -704,7 +695,7 @@
<string name="permission_hotspot_location_request_body">За да създаде безжична точка за достъп, на Briar му е необходимо разрешение за достъп до местоположението.\n\nBriar не го пази и не го споделя с никого.</string>
<string name="permission_hotspot_location_denied_body">Отказахте достъп до местоположението, но то е необходимо за създаване на безжична точка за достъп.\n\nОбмислете дали да не дадете разрешение.</string>
<string name="wifi_settings_title">Настройки на Wi-Fi</string>
<string name="wifi_settings_request_enable_body">За да създаде безжична точка за достъп, Briar се нуждае от безжична мрежа. Включете Wi-Fi.</string>
<string name="wifi_settings_request_enable_body">За създаване на безжична точка за достъп Briar се нуждае от Wi-Fi. Включете го.</string>
<string name="hotspot_tab_manual">Ръчно</string>
<!--The placeholder to be inserted into the string 'hotspot_manual_wifi': People can connect by %s-->
<string name="hotspot_scanning_a_qr_code">като сканират код за QR</string>
@@ -724,10 +715,9 @@
<string name="hotspot_manual_site_address">Адрес (URL)</string>
<string name="hotspot_qr_site">Устройството ви създава безжична точка за достъп. Хората, които са свързани към нея могат да изтеглят Briar като сканират този код за QR.</string>
<!--e.g. Download Briar 1.2.20-->
<string name="website_download_title_1">Изтегляне на Briar %s</string>
<string name="website_download_intro_1">Някой наблизо споделя Briar с вас.</string>
<string name="website_download_button">Изтегляне на Briar</string>
<string name="website_download_outro">След като файлът бъде изтеглен, го отворете и го инсталирайте.</string>
<string name="website_download_title">Изтеглете %s</string>
<string name="website_download_intro">Някой наблизо споделя с вас %s.</string>
<string name="website_download_outro">След като файлът се изтегли, отворете го и го инсталирайте.</string>
<string name="website_troubleshooting_title">Отстраняване на неизправности</string>
<string name="website_troubleshooting_1">Ако не можете да изтеглите приложението пробвайте с друг мрежов четец.</string>
<string name="hotspot_help_wifi_title">Проблеми при свързване чрез Wi-Fi:</string>
@@ -736,7 +726,7 @@
<string name="hotspot_help_wifi_3">Рестартирайте устройството, което създава безжичната точка за достъп, отворете Briar и го споделете отново.</string>
<string name="hotspot_help_site_title">Проблеми при посещаване на страницата:</string>
<string name="hotspot_help_site_1">Уверете се, че въвеждате адреса точно, както е показан. Дори малка грешка може да доведе до неуспех.</string>
<string name="hotspot_help_site_2">Уверете се, че устройството е свързано с правилната безжична мрежа (вижте по-горе) докато отваряте страницата.</string>
<string name="hotspot_help_site_2">Уверете се, че устройството е свързано с правилната мрежа на Wi-Fi (вижте по-горе) докато отваряте страницата.</string>
<string name="hotspot_help_site_3">Ако имате инсталирано приложение за защитна стена се уверете, че не спира достъпа.</string>
<string name="hotspot_help_site_4">Ако можете да отворите страницата, но не и да изтеглите приложението на Briar пробвайте с друг мрежов четец.</string>
<string name="hotspot_help_fallback_title">Нищо не става?</string>
@@ -751,11 +741,8 @@
<string name="hotspot_error_start_callback_no_group_info">Безжичната точка не може да стартира: няма информация за група</string>
<string name="hotspot_error_web_server_start">Грешка при стартиране на уеб сървър</string>
<string name="hotspot_flag_test">Внимание: Това приложение е инсталирано с Android Studio и НЕ може да бъде инсталирано на друго устройство.</string>
<string name="hotspot_error_framework_busy">Безжичната точка не може да бъде стартирана.\n\nАко има включена друга безжична точка за достъп или споделяте мобилните си данни по безжичен път, опитайте да ги спрете и пробвайте отново.</string>
<!--Transfer Data via Removable Drives-->
<string name="removable_drive_menu_title">Свързване чрез преносим диск</string>
<string name="removable_drive_intro">Ако не можете да се свържете с контакта си чрез интернет, безжична мрежа или Bluetooth, Briar може да прехвърли съобщенията и на преносим диск, например памет на USB или SD карта.</string>
<string name="removable_drive_explanation">Ако не можете да се свържете с контакта си чрез интернет, безжична мрежа или Bluetooth, Briar може да прехвърли съобщенията и на преносим диск, например памет на USB или SD карта.\n\nКогато използвате бутона „Изпращане на сведения“, всичко, коо чака да бъде изпратено на контакта, ще бъде записано на преносимия диск. Това включва лични съобщения, прикачени файлове, блогове, форуми и частни групи.\n\nПреди да бъде записано на преносимия диск, всичко ще бъде шифровано.\n\nКогато контактът ви получи сменяемото устройство, за да внесе съобщенията в Briar, той може да използва бутона „Получаване на сведения“.</string>
<string name="removable_drive_title_send">Изпращане на сведения</string>
<string name="removable_drive_title_receive">Получаване на сведения</string>
<string name="removable_drive_send_intro">Докоснете бутона по-долу, за да бъде създаден файл, който ще съдържа шифрованите съобщения. Можете да изберете къде да бъде запазен този файл.\n\nАко желаете да го запазите на преносим диск го включете сега.</string>

View File

@@ -26,10 +26,7 @@
<string name="dnkm_xiaomi_button">Briar schützen</string>
<string name="dnkm_xiaomi_help">Wenn Briar nicht in der Liste der zuletzt verwendeten Apps gesperrt ist, kann es nicht im Hintergrund ausgeführt werden.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Öffne die Liste der zuletzt verwendeten Apps (auch \'App Switcher\' genannt)\n\n2. Wische auf dem Bild von Briar nach unten, um das Vorhängeschlosssymbol anzuzeigen\n\n3. Wenn das Vorhängeschloss nicht gesperrt ist, tippe darauf, um es zu sperren</string>
<string name="dnkm_xiaomi_dialog_body_new">1. Öffne die Liste der zuletzt verwendeten Apps (auch \'App Switcher\' genannt)\n\n2. Wenn neben Briar ein kleines Vorhängeschloss angezeigt wird, brauchst du nichts zu tun.\n\n3. Wenn kein Vorhängeschloss vorhanden ist, halte das Bild von Briar gedrückt, bis die Schaltfläche für das Vorhängeschloss erscheint, und tippe dann darauf</string>
<string name="dnkm_xiaomi_lock_apps_text">Bitte tippe auf die Schaltfläche unten, um die Sicherheitseinstellungen zu öffnen. Tippe auf \"Geschwindigkeit erhöhen\", dann auf \"Apps sperren\" und stelle sicher, dass Briar auf \"Gesperrt\" gesetzt ist.</string>
<string name="dnkm_xiaomi_lock_apps_help">Wenn Briar nicht auf \" Gesperrt\" im Bereich \"Apps sperren\" gesetzt wurde, kann sie nicht im Hintergrund ausgeführt werden.</string>
<string name="dnkm_warning_dozed_1">Briar konnte nicht im Hintergrund ausgeführt werden</string>
<string name="dnkm_xiaomi_dialog_body_new">1. Öffne die Liste der zuletzt verwendeten Apps (auch \'App Switcher\' genannt)\n\n2. Halte das Bild von Briar gedrückt, bis die Schaltfläche für das Vorhängeschloss angezeigt wird\n\n3. Wenn das Vorhängeschloss nicht gesperrt ist, tippe darauf, um es zu sperren</string>
<!--Login-->
<string name="enter_password">Passwort</string>
<string name="try_again">Passwort falsch, bitte erneut versuchen</string>
@@ -172,11 +169,11 @@
<string name="set_contact_alias_hint">Name des Kontakts</string>
<string name="menu_item_disappearing_messages">Selbstlöschende Nachrichten</string>
<!--The first placeholder will show a duration like "7 days". The second placeholder at the end will add "Tap to learn more."-->
<string name="auto_delete_msg_you_enabled">Deine Nachrichten werden nach %1$s gelöscht. %2$s</string>
<string name="auto_delete_msg_you_enabled">Deine Nachrichten werden nach %1$sgelöscht. %2$s</string>
<!--The placeholder at the end will add "Tap to learn more."-->
<string name="auto_delete_msg_you_disabled">Deine Nachrichten werden nicht gelöscht. %1$s</string>
<!--The first placeholder will show a contact's name. The second placeholder will show a duration like "7 days". The third placeholder at the end will add "Tap to learn more."-->
<string name="auto_delete_msg_contact_enabled">%1$s\'s Nachrichten werden nach %2$s gelöscht. %3$s</string>
<string name="auto_delete_msg_contact_enabled">%1$s\'s Nachrichten werden nach%2$sgelöscht. %3$s</string>
<plurals name="duration_minutes">
<item quantity="one">%d Minute</item>
<item quantity="other">%d Minuten</item>
@@ -233,8 +230,8 @@
<string name="contact_added_toast">Kontakt hinzugefügt: %s</string>
<string name="contact_already_exists">Kontakt %s existiert bereits</string>
<string name="qr_code_invalid">Der QR-Code ist ungültig</string>
<string name="qr_code_too_old_1">Der QR-Code, den du gescannt hast, kommt von einer älteren Version von Briar.\n\nBitte deinen Kontakt, zur neuesten Version upzudaten, und versuche es erneut.</string>
<string name="qr_code_too_new_1">Der QR-Code, den du gescannt hast, kommt von einer neueren Version.\n\nBitte aktualisiere Briar auf die neueste Version und versuche es erneut.</string>
<string name="qr_code_too_old">Der QR-Code, den du eingescannt hast, kommt von einer älteren Version von %s.\n\nBitte deinen Kontakt, zur neuesten Version upzudaten, und probiere es erneut.</string>
<string name="qr_code_too_new">Der QR-Code, den du gescannt hast, kommt von einer neueren Version.\n\nBitte aktualisiere %s auf die neueste Version und versuche es erneut.</string>
<string name="camera_error">Kamerafehler</string>
<string name="connecting_to_device">Verbinde mit Gerät\u2026</string>
<string name="authenticating_with_device">Authentifiziere Gerät\u2026</string>
@@ -310,7 +307,7 @@
<string name="introduction_error">Es gab einen Fehler beim Versuch, die Kontaktempfehlung zu verschicken.</string>
<string name="introduction_request_sent">Du wolltest %1$s an %2$s als Kontakt empfehlen</string>
<string name="introduction_request_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. Möchtest du %2$s zu deiner Kontaktliste hinzufügen?</string>
<string name="introduction_request_exists_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. %2$s ist allerdings bereits in deiner Kontaktliste. Da %1$s das vielleicht nicht weiß, kannst du trotzdem antworten:</string>
<string name="introduction_request_exists_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. %2$s ist allerdings bereits in deiner Kontaktliste. Da %1$s das vielleicht nicht weiss, kannst du trotzdem antworten:</string>
<string name="introduction_request_answered_received">%1$s schlägt vor, dich als Kontakt an %2$s zu empfehlen. </string>
<string name="introduction_response_accepted_sent">Du hast die Kontaktempfehlung von %1$s angenommen.</string>
<string name="introduction_response_accepted_sent_info">Bevor %1$s zu deinen Kontakten hinzugefügt wird, muss die Kontaktempfehlung ebenfalls akzeptieren werden. Dies kann eine Weile dauern.</string>
@@ -415,7 +412,7 @@
<string name="forum_share_error">Es gab einen Fehler beim Versuch, dieses Forum zu teilen.</string>
<string name="forum_invitation_received">%1$s hat das Forum \"%2$s\" mit dir geteilt.</string>
<string name="forum_invitation_sent">Du hast das Forum \"%1$s\" mit %2$s geteilt.</string>
<string name="forum_invitations_title">Foreneinladungen</string>
<string name="forum_invitations_title">Forumeinladungen</string>
<string name="forum_invitation_exists">Du hast bereits eine Einladung zu diesem Forum angenommen.\n\nMehrere Einladungen anzunehmen wird deine Verbindung zu diesem Forum schneller und zuverlässiger machen.</string>
<string name="forum_joined_toast">Dem Forum beigetreten</string>
<string name="forum_declined_toast">Einladung abgelehnt</string>
@@ -479,7 +476,7 @@
<string name="blogs_rss_feeds_manage_updated">Letzte Aktualisierung:</string>
<string name="blogs_rss_remove_feed">Feed entfernen</string>
<string name="blogs_rss_remove_feed_dialog_message">Bist du sicher, dass du diesen Feed löschen willst?\n\nBeiträge werden von deinem Gerät entfernt, aber nicht von den Geräten anderer Personen.\n\nAlle Kontakte, für die du diesen Feed freigegeben hast, können keine Updates mehr erhalten.</string>
<string name="blogs_rss_remove_feed_ok">Entfernen</string>
<string name="blogs_rss_remove_feed_ok">Aufheben</string>
<string name="blogs_rss_feeds_manage_empty_state">Keine RSS-Feeds vorhanden\n\nTippe auf das + Symbol, um einen Feed zu importieren</string>
<string name="blogs_rss_feeds_manage_error">Es gab ein Problem beim Laden deiner Feeds. Bitte versuche es später erneut.</string>
<!--Settings Profile Picture-->
@@ -605,10 +602,6 @@
<string name="mailbox_status_connected_title">Mailbox läuft</string>
<string name="mailbox_status_problem_title">Briar hat Probleme mit der Verbindung zur Mailbox</string>
<string name="mailbox_status_failure_title">Mailbox ist nicht verfügbar</string>
<string name="mailbox_status_app_too_old_title">Briar ist zu alt</string>
<string name="mailbox_status_app_too_old_message">Aktualisiere Briar auf die neueste Version der App und versuche es erneut.</string>
<string name="mailbox_status_mailbox_too_old_title">Mailbox ist zu alt</string>
<string name="mailbox_status_mailbox_too_old_message">Aktualisiere deine Mailbox auf die neueste Version der App und versuche es erneut.</string>
<string name="mailbox_status_check_button">Verbindung überprüfen</string>
<!--Example for string substitution: Last connection: 3min ago-->
<string name="mailbox_status_connected_info">Letzte Verbindung: %s</string>
@@ -620,30 +613,8 @@
<string name="mailbox_status_unlink_dialog_warning">Wenn du die Verknüpfung mit deiner Mailbox aufhebst, kannst du keine Nachrichten empfangen, während Briar offline ist.</string>
<string name="mailbox_status_unlink_no_wipe_title">Deine Mailbox wurde getrennt</string>
<string name="mailbox_status_unlink_no_wipe_message">Wenn du das nächste Mal Zugriff auf dein Mailbox-Gerät hast, öffne bitte die Mailbox-App und tippe auf die Schaltfläche \"Trennen\", um den Vorgang abzuschließen.\n\nWenn du keinen Zugriff mehr auf dein Mailbox-Gerät hast, mach dir keine Sorgen. Deine Daten sind verschlüsselt, sodass sie sicher bleiben, auch wenn du den Vorgang nicht abschließt.</string>
<string name="mailbox_error_notification_channel_title">Briar Mailbox Problem</string>
<string name="mailbox_error_notification_title">Briar Mailbox ist nicht verfügbar</string>
<string name="mailbox_error_notification_text">Antippen, um das Problem zu beheben.</string>
<string name="mailbox_error_wizard_button">Problem beheben</string>
<string name="mailbox_error_wizard_title">Assistent zur Fehlerbehebung für die Mailbox</string>
<string name="mailbox_error_wizard_question1">Hast du Zugriff auf dein Mailbox-Gerät?</string>
<string name="mailbox_error_wizard_answer1">Ja, ich habe jetzt Zugriff darauf.</string>
<string name="mailbox_error_wizard_answer2">Im Moment nicht, aber ich kann später Zugriff darauf bekommen.</string>
<string name="mailbox_error_wizard_answer3">Nein, ich habe keinen Zugriff mehr darauf.</string>
<string name="mailbox_error_wizard_info1_1">Überprüfe, ob das Mailbox-Gerät eingeschaltet und mit dem Internet verbunden ist.</string>
<string name="mailbox_error_wizard_question1_1">Öffne die Mailbox-App. Was siehst du?</string>
<string name="mailbox_error_wizard_answer1_1">Ich sehe eine Anleitung zum Einrichten der Mailbox</string>
<string name="mailbox_error_wizard_answer1_2">Ich sehe einen QR-Code</string>
<string name="mailbox_error_wizard_answer1_3">Ich sehe \"Mailbox wird ausgeführt\"</string>
<string name="mailbox_error_wizard_answer1_4">Ich sehe \"Gerät ist offline\"</string>
<string name="mailbox_error_wizard_info1_1_1">Bitte trenne die Verknüpfung deiner Mailbox über die Schaltfläche unten und folge dann den Anweisungen auf dem Mailbox-Gerät, um sie erneut zu verbinden.</string>
<string name="mailbox_error_wizard_info_1_1_2">Bitte trenne die Verknüpfung deiner Mailbox über die Schaltfläche unten und scanne dann den QR-Code, um sie erneut zu verbinden.</string>
<string name="mailbox_error_wizard_info1_1_3">Bitte benutze die Schaltfläche unten, um die Verbindung zwischen Briar und der Mailbox zu überprüfen.\n\n
Wenn die Verbindung erneut fehlschlägt:\n
\u2022 Überprüfe, ob die Mailbox- und Briar-Apps auf die neueste Version aktualisiert sind.\n
\u2022 Starte deine Mailbox- und Briar-Geräte neu und versuche es erneut.</string>
<string name="mailbox_error_wizard_info1_1_4">Überprüfe, ob das Mailbox-Gerät ordnungsgemäß mit dem Internet verbunden ist.\n\nÜberprüfe, ob die Uhr auf dem Mailbox-Gerät die richtige Uhrzeit, das richtige Datum und die richtige Zeitzone anzeigt.\n\nÜberprüfe, ob die Mailbox- und Briar-Apps auf die neueste Version aktualisiert sind.\n\nStarte deine Mailbox- und Briar-Geräte neu und versuche es erneut.</string>
<string name="mailbox_error_wizard_info2">Bitte rufe diesen Bildschirm wieder auf, wenn du Zugriff auf das Gerät hast.</string>
<string name="mailbox_error_wizard_info3">Bitte trenne die Verknüpfung deiner Mailbox über die Schaltfläche unten.\n\nNach der Trennung deiner alten Mailbox kannst du jederzeit eine neue Mailbox einrichten.</string>
<!--Conversation Settings-->
<string name="disappearing_messages_title">Selbstlöschende Nachrichten</string>
<string name="disappearing_messages_explanation_long">Wenn diese Einstellung aktiviert ist, werden neue
@@ -692,7 +663,7 @@
<string name="send_report">Bericht senden</string>
<string name="close">Schließen</string>
<string name="dev_report_sending">Rückmeldung wird gesendet…</string>
<string name="dev_report_sent">Feedback gesendet</string>
<string name="dev_report_sent">Feedback senden</string>
<string name="dev_report_saved">Der Bericht wurde gespeichert. Er wird verschickt, wenn du dich das nächste Mal bei Briar anmeldest.</string>
<string name="dev_report_error">Fehler: Senden des Reports fehlgeschlagen</string>
<!--Sign Out-->
@@ -711,7 +682,7 @@
<string name="permission_camera_location_title">Kamera und Standort</string>
<string name="permission_camera_location_request_body">Um den QR-Code zu scannen, braucht Briar Zugriff auf die Kamera.\n\nUm Bluetooth-Geräte zu finden, braucht Briar Zugriff auf deinen Standort.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string>
<string name="permission_camera_denied_body">Du hast den Zugriff auf die Kamera verweigert, aber das Hinzufügen von Kontakten erfordert die Verwendung der Kamera.\n\nBitte gewähre den Zugriff.</string>
<string name="permission_location_denied_body">Du hast den Zugriff auf deinen Standort verweigert, aber Briar benötigt diese Berechtigung, um Bluetooth-Geräte zu erkennen.\n\nBitte überlege, ob du Zugriff gewährst.</string>
<string name="permission_location_denied_body">Du hast den Zugriff auf deinen Standort verweigert, aber Briar benötigt diese Berechtigung, um Bluetooth-Geräte zu erkennen.\n\nBitte gewähre den Zugriff.</string>
<string name="permission_location_setting_title">Standorteinstellung</string>
<string name="permission_location_setting_body">Die Standorteinstellung deines Geräts muss aktiviert sein, um andere Geräte über Bluetooth zu finden. Bitte aktiviere den Standort, um fortzufahren. Du kannst sie anschließend wieder deaktivieren.</string>
<string name="permission_location_setting_button">Standort aktivieren</string>
@@ -737,7 +708,7 @@
<string name="hotspot_notification_title">Briar offline teilen</string>
<string name="hotspot_button_connected">Weiter</string>
<string name="permission_hotspot_location_request_body">Um einen WLAN-Hotspot zu erstellen, benötigt Briar die Erlaubnis, auf deinen Standort zuzugreifen.\n\nBriar speichert weder deinen Standort noch gibt es ihn an andere weiter.</string>
<string name="permission_hotspot_location_denied_body">Du hast den Zugriff auf deinen Standort verweigert, aber Briar benötigt diese Berechtigung, um einen WLAN-Hotspot zu erstellen.\n\nBitte überlege, ob du Zugriff gewährst.</string>
<string name="permission_hotspot_location_denied_body">Du hast den Zugriff auf deinen Standort verweigert, aber Briar benötigt diese Berechtigung, um einen WLAN-Hotspot zu erstellen.\n\nBitte gewähre den Zugriff.</string>
<string name="wifi_settings_title">WLAN-Einstellungen</string>
<string name="wifi_settings_request_enable_body">Um einen WLAN-Hotspot zu erstellen, benötigt Briar das WLAN. Bitte aktiviere es.</string>
<string name="hotspot_tab_manual">Manuell</string>
@@ -759,9 +730,8 @@
<string name="hotspot_manual_site_address">Adresse (URL)</string>
<string name="hotspot_qr_site">Dein Telefon stellt einen WLAN-Hotspot bereit. Personen, die mit dem Hotspot verbunden sind, können Briar durch Scannen dieses QR-Codes herunterladen.</string>
<!--e.g. Download Briar 1.2.20-->
<string name="website_download_title_1">Briar %s herunterladen</string>
<string name="website_download_intro_1">Jemand in der Nähe hat Briar mit dir geteilt.</string>
<string name="website_download_button">Briar herunterladen</string>
<string name="website_download_title">Download %s</string>
<string name="website_download_intro">Jemand in der Nähe hat %s mit dir geteilt.</string>
<string name="website_download_outro">Nach Abschluss des Downloads öffne die heruntergeladene Datei und installiere sie.</string>
<string name="website_troubleshooting_title">Fehlerbehebung</string>
<string name="website_troubleshooting_1">Wenn du die App nicht herunterladen kannst, versuche es mit einer anderen Webbrowser-App.</string>

View File

@@ -1,31 +1,36 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--Setup-->
<string name="setup_title">Bienvenido a Briar</string>
<string name="setup_name_explanation">Tu nombre de usuario aparecerá junto a cualquier contenido que publiques. No puedes cambiarlo después de crear tu cuenta.</string>
<string name="setup_next">Siguiente</string>
<string name="setup_password_intro">Elige una contraseña</string>
<string name="setup_password_explanation">Tu cuenta Briar se almacena cifrada en tu dispositivo, no en la nube. Si olvidas tu contraseña o desinstalas Briar, no hay manera de recuperarla.\n\nElige una contraseña larga que sea difícil de adivinar, como cuatro palabras aleatorias o diez letras, números y símbolos al azar.</string>
<string name="dnkm_doze_intro">Para recibir mensajes, Briar necesita mantenerse conectado en segundo plano.</string>
<string name="dnkm_doze_explanation">Para recibir mensajes, Briar necesita mantenerse conectado en segundo plano. Desactiva las optimizaciones de la batería para que Briar pueda permanecer conectado.</string>
<string name="choose_nickname">Elige tu nombre de usuario</string>
<string name="choose_password">Elige tu contraseña</string>
<string name="confirm_password">Confirma tu contraseña</string>
<string name="name_too_long">El nombre es demasiado largo</string>
<string name="password_too_weak">La contraseña es demasiado débil</string>
<string name="passwords_do_not_match">Las contraseñas no coinciden</string>
<string name="create_account_button">Crea una cuenta</string>
<string name="more_info">Más información</string>
<string name="don_t_ask_again">No preguntes de nuevo</string>
<string name="dnkm_huawei_protected_text">Por favor pulsa el botón de abajo y asegúrate de que Briar está protegido en la pantalla \"Aplicaciones protegidas\".</string>
<string name="dnkm_huawei_protected_button">Proteger Briar</string>
<string name="dnkm_huawei_protected_help">Si Briar no se agrega a la lista de aplicaciones protegidas, no podrá ejecutarse en segundo plano.</string>
<string name="dnkm_huawei_app_launch_text">Por favor, pulsa el botón de abajo, abre la pantalla \"lanzamiento de aplicación\" y asegúrate de que Briar esté configurado como \"Gestionar manualmente\".</string>
<string name="dnkm_huawei_app_launch_help">Si Briar no está configurado como \"Gestionar manualmente\" en la pantalla \"lanzamiento de aplicación\", no será capaz de ejecutarse en segundo plano.</string>
<string name="dnkm_xiaomi_text">Para ejecutarse en segundo plano, Briar necesita estar bloqueado en la lista de aplicaciones recientes.</string>
<string name="dnkm_xiaomi_button">Proteger Briar</string>
<string name="dnkm_xiaomi_help">Si Briar no está bloqueado en la lista de aplicaciones recientes, no podrá ejecutarse en segundo plano.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Abre la lista de aplicaciones recientes (también llamada selectora de aplicaciones)\n\n2. Desliza hacia abajo sobre la imagen de Briar para mostrar el ícono de un candado\n\n3. Si el candado no está bloqueado, púlsalo para hacerlo</string>
<resources>
<!--Setup-->
<string name="setup_title">Bienvenido a Briar</string>
<string name="setup_name_explanation">Tu nombre de usuario aparecerá junto a cualquier contenido que publiques. No puedes cambiarlo después de crear tu cuenta.</string>
<string name="setup_next">Siguiente</string>
<string name="setup_password_intro">Elige una contraseña</string>
<string name="setup_password_explanation">Tu cuenta Briar se almacena cifrada en tu dispositivo, no en la nube. Si olvidas tu contraseña o desinstalas Briar, no hay manera de recuperarla.\n\nElige una contraseña larga que sea difícil de adivinar, como cuatro palabras aleatorias o diez letras, números y símbolos al azar.</string>
<string name="dnkm_doze_title">Conexiones de segundo plano</string>
<string name="dnkm_doze_intro">Para recibir mensajes, Briar necesita mantenerse conectado en segundo plano.</string>
<string name="dnkm_doze_explanation">Para recibir mensajes, Briar necesita mantenerse conectado en segundo plano. Desactiva las optimizaciones de la batería para que Briar pueda permanecer conectado.</string>
<string name="dnkm_doze_button">Permitir conexiones</string>
<string name="choose_nickname">Elige tu nombre de usuario</string>
<string name="choose_password">Elige tu contraseña</string>
<string name="confirm_password">Confirma tu contraseña</string>
<string name="name_too_long">El nombre es demasiado largo</string>
<string name="password_too_weak">La contraseña es demasiado débil</string>
<string name="passwords_do_not_match">Las contraseñas no coinciden</string>
<string name="create_account_button">Crea una cuenta</string>
<string name="more_info">Más información</string>
<string name="don_t_ask_again">No preguntes de nuevo</string>
<string name="dnkm_huawei_protected_text">Por favor pulsa el botón de abajo y asegúrate de que Briar está protegido en la pantalla \"Aplicaciones protegidas\".</string>
<string name="dnkm_huawei_protected_button">Proteger Briar</string>
<string name="dnkm_huawei_protected_help">Si Briar no se agrega a la lista de aplicaciones protegidas, no podrá ejecutarse en segundo plano.</string>
<string name="dnkm_huawei_app_launch_text">Por favor, pulsa el botón de abajo, abre la pantalla \"lanzamiento de aplicación\" y asegúrate de que Briar esté configurado como \"Gestionar manualmente\".</string>
<string name="dnkm_huawei_app_launch_button">Abrir Ajustes de Batería</string>
<string name="dnkm_huawei_app_launch_help">Si Briar no está configurado como \"Gestionar manualmente\" en la pantalla \"lanzamiento de aplicación\", no será capaz de ejecutarse en segundo plano.</string>
<string name="dnkm_xiaomi_text">Para ejecutarse en segundo plano, Briar necesita estar bloqueado en la lista de aplicaciones recientes.</string>
<string name="dnkm_xiaomi_button">Proteger Briar</string>
<string name="dnkm_xiaomi_help">Si Briar no está bloqueado en la lista de aplicaciones recientes, no podrá ejecutarse en segundo plano.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Abre la lista de aplicaciones recientes (también llamada selectora de aplicaciones)\n\n2. Desliza hacia abajo sobre la imagen de Briar para mostrar el ícono de un candado\n\n3. Si el candado no está bloqueado, púlsalo para hacerlo</string>
<string name="dnkm_xiaomi_dialog_body_new">1. Abre la lista de aplicaciones recientes (también llamada selectora de aplicaciones)\n\n2. Presiona y mantén la imagen de Briar hasta que aparezca el botón de un candado\n\n3. Si el candado no está bloqueado, púlsalo para hacerlo</string>
<string name="dnkm_warning_dozed">%s no pudo ejecutarse en segundo plano</string>
<!--Login-->
<string name="enter_password">Contraseña</string>
<string name="try_again">Contraseña incorrecta, inténtalo de nuevo</string>
@@ -43,12 +48,10 @@
<string name="startup_failed_service_error">Briar no fue capaz de iniciar un componente requerido.\n\nPor favor, actualiza a la última versión de la aplicación e inténtalo de nuevo.</string>
<plurals name="expiry_warning">
<item quantity="one">Esta es una versión de prueba de Briar. Su cuenta expirará en %d día y no podrá ser renovada.</item>
<item quantity="many">Esta es una versión de prueba de Briar. Tu cuenta expirará en %d días y no podrá ser renovada.</item>
<item quantity="other">Esta es una versión de prueba de Briar. Tu cuenta expirará en %d días y no podrá ser renovada.</item>
</plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 ya no es soportado. Briar dejará de funcionar sobre %s (en %d días). Por favor, instala Briar en un dispositivo más nuevo y crea una cuenta nueva.</item>
<item quantity="many">Android 4 ya no es soportado. Briar dejará de funcionar sobre %s (en %d días). Por favor, instala Briar en un dispositivo más nuevo y crea una cuenta nueva.</item>
<item quantity="other">Android 4 ya no es soportado. Briar dejará de funcionar sobre %s (en %d días). Por favor, instala Briar en un dispositivo más nuevo y crea una cuenta nueva.</item>
</plurals>
<string name="expiry_date_reached">Este programa ha caducado.\n¡Gracias por probarlo!</string>
@@ -111,22 +114,18 @@
<string name="ongoing_notification_text">Toca para abrir Briar.</string>
<plurals name="private_message_notification_text">
<item quantity="one">Tienes un nuevo mensaje privado.</item>
<item quantity="many">Tienes %d nuevos mensajes privados.</item>
<item quantity="other">Tienes %d nuevos mensajes privados.</item>
</plurals>
<plurals name="group_message_notification_text">
<item quantity="one">Tienes un nuevo mensaje de grupo.</item>
<item quantity="many">Tienes %d nuevos mensajes de grupo.</item>
<item quantity="other">Tienes %d nuevos mensajes de grupo.</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">Hay una nueva publicación en el foro.</item>
<item quantity="many">Hay %d nuevas publicaciones en el foro.</item>
<item quantity="other">Hay %d nuevas publicaciones en el foro.</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="one">Hay una nueva entrada de blog.</item>
<item quantity="many">Hay %d nuevos artículos de blog.</item>
<item quantity="other">Hay %d nuevos artículos de blog.</item>
</plurals>
<!--Misc-->
@@ -181,17 +180,14 @@
<string name="auto_delete_msg_contact_enabled">Los mensajes de %1$s desaparecerán después de %2$s. %3$s</string>
<plurals name="duration_minutes">
<item quantity="one">%d minuto</item>
<item quantity="many">%d minutos</item>
<item quantity="other">%d minutos</item>
</plurals>
<plurals name="duration_hours">
<item quantity="one">%d hora</item>
<item quantity="many">%d horas</item>
<item quantity="other">%d horas</item>
</plurals>
<plurals name="duration_days">
<item quantity="one">%d día</item>
<item quantity="many">%d días</item>
<item quantity="other">%d días</item>
</plurals>
<!--The first placeholder will show a contact's name. The second placeholder at the end will add "Tap to learn more."-->
@@ -238,6 +234,8 @@
<string name="contact_added_toast">Contacto añadido: %s</string>
<string name="contact_already_exists">El contacto %s ya existe</string>
<string name="qr_code_invalid">El código QR no es válido</string>
<string name="qr_code_too_old">El código QR que has escaneado proviene de una versión anterior de %s.\n\nPor favor solicita a tu contacto que actualice a la última versión y luego intenta nuevamente.</string>
<string name="qr_code_too_new">El código QR que has escaneado proviene de una versión posterior de %s.\n\nPor favor actualiza a la última versión y luego intenta nuevamente.</string>
<string name="camera_error">Error de cámara</string>
<string name="connecting_to_device">Conectando al dispositivo\u2026</string>
<string name="authenticating_with_device">Autentificándose con el dispositivo\u2026</string>
@@ -282,7 +280,6 @@
<string name="step_2">2</string>
<plurals name="contact_added_notification_text">
<item quantity="one">Nuevo contacto añadido.</item>
<item quantity="many">%d nuevos contactos añadidos.</item>
<item quantity="other">%d nuevos contactos añadidos.</item>
</plurals>
<string name="offline_state">No hay conexión a Internet</string>
@@ -303,6 +300,7 @@
<string name="pending_contact_updated_toast">Contacto pendiente actualizado</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Presenta a tus contactos</string>
<string name="introduction_onboarding_text">Presenta a tus contactos entre sí para ahorrarles encontrarse en persona para poder conectar mediante Briar.</string>
<string name="introduction_menu_item">Hacer presentación</string>
<string name="introduction_activity_title">Seleccionar contacto</string>
<string name="introduction_not_possible">Ya tienes una presentación en curso con estos contactos. Por favor, deja que esto termine primero. Si tu o tus contactos raramente están en línea, esto puede tomar algún tiempo.</string>
@@ -338,7 +336,6 @@
<string name="groups_created_by">Creado por %s</string>
<plurals name="messages">
<item quantity="one">%d mensaje</item>
<item quantity="many">%d mensajes</item>
<item quantity="other">%d mensajes</item>
</plurals>
<string name="groups_group_is_empty">Este grupo está vacío</string>
@@ -372,7 +369,6 @@
<string name="groups_invitations_declined">Invitación de grupo declinada</string>
<plurals name="groups_invitations_open">
<item quantity="one">%d invitación de grupo sin responder</item>
<item quantity="many">%d invitaciones de grupo sin responder</item>
<item quantity="other">%d invitaciones de grupo sin responder</item>
</plurals>
<string name="groups_invitations_response_accepted_sent">Aceptaste la invitación de grupo de %s.</string>
@@ -399,7 +395,6 @@
<string name="no_posts">Sin publicaciones</string>
<plurals name="posts">
<item quantity="one">%d publicación</item>
<item quantity="many">%d publicaciones</item>
<item quantity="other">%d publicaciones</item>
</plurals>
<string name="forum_new_message_hint">Nueva publicación</string>
@@ -437,7 +432,6 @@
<string name="shared_with">Compartido con %1$d (%2$d en línea)</string>
<plurals name="forums_shared">
<item quantity="one">%d foro compartido por contactos</item>
<item quantity="many">%d foros compartidos por contactos</item>
<item quantity="other">%d foros compartidos por contactos</item>
</plurals>
<string name="nobody">Nadie</string>
@@ -594,29 +588,9 @@
<string name="mailbox_setup_connecting">Conectando...</string>
<string name="mailbox_setup_qr_code_wrong_title">Código QR erróneo</string>
<string name="mailbox_setup_qr_code_wrong_description">El código escaneado no es válido. Por favor abre la aplicación Buzón de Briar en tu dispositivo de buzón y escanea el código QR que presenta.</string>
<string name="mailbox_setup_already_paired_title">Buzón ya vinculado</string>
<string name="mailbox_setup_already_paired_description">Desvincula el Buzón en tu otro dispositivo e inténtalo de nuevo.</string>
<string name="mailbox_setup_io_error_title">No se pudo conectar</string>
<string name="mailbox_setup_io_error_description">Asegúrate de que ambos dispositivos estén conectados a Internet e inténtalo de nuevo.</string>
<string name="mailbox_setup_assertion_error_title">Error del buzón</string>
<string name="mailbox_setup_assertion_error_description">Por favor, envía tus comentarios (con datos anónimos) a través de la aplicación Briar si la dificultad persiste.</string>
<string name="mailbox_setup_camera_error_description">No se pudo acceder a la cámara. Inténtalo de nuevo, tal vez después de reiniciar el dispositivo.</string>
<string name="mailbox_setup_paired_title">Conectado</string>
<string name="mailbox_setup_paired_description">Tu Buzón ha sido vinculado exitosamente con Briar.\n
\nMantén tu Buzón conectado al suministro eléctrico y al Wi-Fi, de modo que siempre esté en línea.</string>
<string name="tor_offline_title">Desconectado</string>
<string name="tor_offline_description">Asegúrate de que este dispositivo esté en línea y las conexiones a Internet estén permitidas.\n
\nLuego, espera a que el ícono de un globo en las configuraciones de conexión se torne verde.</string>
<string name="tor_offline_description">Asegúrate de que este dispositivo esté en línea y las conexiones a Internet estén permitidas.\n\nLuego, espera a que el ícono de un globo en las configuraciones de conexión se torne verde.</string>
<string name="tor_offline_button_check">Comprobar configuración de conexión</string>
<string name="mailbox_status_title">Estado del buzón</string>
<string name="mailbox_status_connected_title">El buzón está en ejecución</string>
<string name="mailbox_status_failure_title">El buzón no está disponible</string>
<string name="mailbox_status_check_button">Comprobar conexión</string>
<!--Example for string substitution: Last connection: 3min ago-->
<string name="mailbox_status_connected_info">Última conexión: %s</string>
<!--Indicates that there never was a connection to the mailbox. Last connection: Never-->
<string name="mailbox_status_connected_never">Nunca</string>
<string name="mailbox_status_unlink_button">Desvincular</string>
<!--Conversation Settings-->
<string name="disappearing_messages_title">Mensajes con caducidad</string>
<string name="disappearing_messages_explanation_long">Activar este ajuste hará que los nuevos
@@ -724,7 +698,6 @@
<string name="hotspot_no_peers_connected">No hay dispositivos conectados</string>
<plurals name="hotspot_peers_connected">
<item quantity="one">%s dispositivo conectado</item>
<item quantity="many">%s dispositivos conectados</item>
<item quantity="other">%s dispositivos conectados</item>
</plurals>
<!--Download link-->
@@ -733,6 +706,8 @@
<string name="hotspot_manual_site_address">Dirección (URL)</string>
<string name="hotspot_qr_site">Tu teléfono está proporcionando un punto de acceso Wi-Fi. Las personas que están conectadas al mismo pueden descargar Briar escaneando este código QR.</string>
<!--e.g. Download Briar 1.2.20-->
<string name="website_download_title">Descargar %s</string>
<string name="website_download_intro">Alguien en las cercanías compartió %s contigo.</string>
<string name="website_download_outro">Luego de que la descarga se complete, abre el archivo descargado e instálalo.</string>
<string name="website_troubleshooting_title">Resolución de problemas</string>
<string name="website_troubleshooting_1">Si no puedes descargar la aplicación, inténtalo con una aplicación de navegación web diferente.</string>

View File

@@ -28,10 +28,7 @@
<string name="dnkm_xiaomi_button">حفاظت از Briar (برایر)</string>
<string name="dnkm_xiaomi_help">اگر برایر (Briar) در لیست اپ‌های اخیر قفل نشود، قادر با اجرا در پس‌زمینه نخواهد بود.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. لیست اپ‌های اخیر (یا app switcher) را باز کنید\n\n2. عکس اپ برایر (Briar) را پایین کشیده تا آیکون قفل نمایش یابد\n\n3. اگر قفل بسته نیست، روی آن بزنید تا قفل شود</string>
<string name="dnkm_xiaomi_dialog_body_new">1. فهرست برنامه‌های اخیر را باز کنید (که به آن app switcher نیز گفته می‌شود)\n\n2. اگر Briar تصویر کوچکی از یک قفل در کنار نام خود دارد، دیگر لازم نیست کاری انجام دهید\n\n3. اگر قفلی وجود ندارد، تصویر Briar را فشار داده و نگه دارید تا دکمه قفل ظاهر شود، سپس روی آن ضربه بزنید.</string>
<string name="dnkm_xiaomi_lock_apps_text">لطفا روی دکمه زیر ضربه بزنید تا تنظیمات امنیتی باز شود. روی \"Boost speed\" ضربه بزنید، سپس روی \"Lock apps\" ضربه بزنید و مطمئن شوید که Briar روی \"Locked\" تنظیم شده است.</string>
<string name="dnkm_xiaomi_lock_apps_help">اگر Briar در صفحه \"Lock apps\" روی \"Locked\" تنظیم نشود، نمی‌تواند در پس‌زمینه اجرا شود.</string>
<string name="dnkm_warning_dozed_1">Briar قادر به اجرا در پس‌زمینه نبود</string>
<string name="dnkm_xiaomi_dialog_body_new">1. لیست اپ‌های اخیر (یا app switcher) را باز کنید\n\n2. اروی عکس اپ برایر (Briar) نگه داشته تا آیکون قفل نمایش یابد\n\n3. اگر قفل بسته نیست، روی آن بزنید تا قفل شود</string>
<!--Login-->
<string name="enter_password">گذرواژه</string>
<string name="try_again">گذرواژه اشتباه است، لطفا دوباره سعی کنید</string>
@@ -243,8 +240,12 @@
<string name="contact_added_toast">مخاطب اضافه شد: %s</string>
<string name="contact_already_exists">مخاطب %s از قبل وجود دارد</string>
<string name="qr_code_invalid">کد کیوآر نامعتبر می باشد</string>
<string name="qr_code_too_old_1">کد QR که اسکن کرده‌اید مربوط به نسخه قدیمی Briar است.\n\nلطفا از مخاطب خود بخواهید به آخرین نسخه ارتقا دهد و سپس دوباره امتحان کنید.</string>
<string name="qr_code_too_new_1">کد QR که اسکن کرده‌اید مربوط به نسخه جدیدتری از Briar است.\n\nلطفا به آخرین نسخه ارتقا دهید و سپس دوباره امتحان کنید.</string>
<string name="qr_code_too_old">کد کیوآر اسکن شده متعلق به یک نسخه قدیمی از %s است.
لطفا از مخاطب‌ خود بخواهید تا به آخرین نسخه ارتقا داده و سپس دوباره تلاش کنید.</string>
<string name="qr_code_too_new">کد کیوآر اسکن شده توسط شما متعلق به یک نسخه جدیدتر از %s است.
لطفا به آخرین نسخه ارتقا داده و دوباره تلاش کنید.</string>
<string name="camera_error">خطای دوربین</string>
<string name="connecting_to_device">اتصال به دستگاهu2026\</string>
<string name="authenticating_with_device">تصدیق سازی با دستگاه u2026\</string>
@@ -637,10 +638,6 @@
<string name="mailbox_status_connected_title">Mailbox در حال اجراست</string>
<string name="mailbox_status_problem_title">Briar در اتصال به صندوق پستی به مشکلی برخورده است</string>
<string name="mailbox_status_failure_title">صندوق پستی در دسترس نیست</string>
<string name="mailbox_status_app_too_old_title">Briar خیلی قدیمی است</string>
<string name="mailbox_status_app_too_old_message">Briar را به آخرین نسخه برنامه به‌روزرسانی کنید و دوباره امتحان کنید.</string>
<string name="mailbox_status_mailbox_too_old_title">Mailbox خیلی قدیمی است</string>
<string name="mailbox_status_mailbox_too_old_message">Mailbox خود را به آخرین نسخه برنامه به‌روزرسانی کنید و دوباره امتحان کنید.</string>
<string name="mailbox_status_check_button">اتصال را بررسی کنید</string>
<!--Example for string substitution: Last connection: 3min ago-->
<string name="mailbox_status_connected_info">آخرین اتصال: %s</string>
@@ -803,9 +800,8 @@ Briar (برایر) موقعیت شما را ذخیره نمی‌کند و آن
<string name="hotspot_manual_site_address">آدرس (URL)</string>
<string name="hotspot_qr_site">تلفن شما Wi-Fi hotspot ارائه می‌کند. کسانی که به hotspot متصل هستند، می‌توانند Briar را با اسکن این کد QR دانلود کنند.</string>
<!--e.g. Download Briar 1.2.20-->
<string name="website_download_title_1">دانلود Briar %s</string>
<string name="website_download_intro_1">شخصی در همین نزدیکی Briar را با شما به اشتراک گذاشته است.</string>
<string name="website_download_button">دانلود Briar</string>
<string name="website_download_title">دانلود %s</string>
<string name="website_download_intro">فردی در نزدیکی %s را با شما به اشتراک گذاشته است.</string>
<string name="website_download_outro">پس از تکمیل دانلود، فایل دانلود شده را باز کرده و نصب کنید.</string>
<string name="website_troubleshooting_title">◾️ عیب یابی</string>
<string name="website_troubleshooting_1">اگر قادر به دانلود کردن برنامه نیستید، با مرورگر دیگری امتحان کنید.</string>

View File

@@ -1,31 +1,37 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--Setup-->
<string name="setup_title">Bienvenue à Briar</string>
<string name="setup_name_explanation">Votre pseudonyme sera affiché à côté de tout contenu que vous publierez. Vous pourrez le modifier après avoir créé votre compte.</string>
<string name="setup_next">Suivant</string>
<string name="setup_password_intro">Choisir un mot de passe</string>
<string name="setup_password_explanation">Votre compte Briar est enregistré chiffré sur votre appareil et non dans le nuage. Si vous oubliez votre mot de passe ou désinstallez Briar, votre compte ne peut pas être récupéré.\n\nChoisissez un mot de passe long qui sera difficile à deviner, par exemple quatre mots au hasard ou dix lettres, chiffres et symboles au hasard.</string>
<string name="dnkm_doze_intro">Pour recevoir des messages, Briar a besoin de rester connectée en arrière-plan.</string>
<string name="dnkm_doze_explanation">Pour recevoir des messages, Briar a besoin de rester connectée en arrière-plan. Veuillez désactiver les optimisations de la pile afin que Briar puisse rester connectée.</string>
<string name="choose_nickname">Choisissez votre pseudonyme</string>
<string name="choose_password">Choisissez votre mot de passe</string>
<string name="confirm_password">Confirmez votre mot de passe</string>
<string name="name_too_long">Le nom est trop long</string>
<string name="password_too_weak">Le mot de passe est trop faible</string>
<string name="passwords_do_not_match">Les mots de passe ne correspondent pas</string>
<string name="create_account_button">Créer un compte</string>
<string name="more_info">Plus dinformations</string>
<string name="don_t_ask_again">Ne plus demander</string>
<string name="dnkm_huawei_protected_text">Veuillez toucher le bouton ci-dessous et vous assurer que Briar est protégée dans lécran « Applis protégées ».</string>
<string name="dnkm_huawei_protected_button">Protéger Briar</string>
<string name="dnkm_huawei_protected_help">Si Briar nest pas ajoutée à la liste des applis protégées, elle ne pourra pas fonctionner en arrière-plan.</string>
<string name="dnkm_huawei_app_launch_text">Veuillez toucher le bouton ci-dessous, ouvrir lécran « Lancement des applis » et vous assurer que « Gérer manuellement » est défini pour Briar.</string>
<string name="dnkm_huawei_app_launch_help">Si « Gérer manuellement » nest pas défini pour Briar dans lécran « Lancement des applis », lappli ne pourra pas fonctionner en arrière-plan.</string>
<string name="dnkm_xiaomi_text">Pour fonctionner en arrière-plan, Briar doit être verrouillée à la liste des applis récentes.</string>
<string name="dnkm_xiaomi_button">Protéger Briar</string>
<string name="dnkm_xiaomi_help">Si Briar nest pas verrouillée à la liste des applis récentes, elle ne pourra pas fonctionner en arrière-plan.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Ouvrez la liste des applis récentes (aussi appelé sélecteur dappli)\n\n2. Balayez limage de Briar vers le bas pour afficher licône de verrou\n\n3. Si le verrou nest pas verrouillé, touchez pour le verrouiller</string>
<resources>
<!--Setup-->
<string name="setup_title">Bienvenue à Briar</string>
<string name="setup_name_explanation">Votre pseudonyme sera affiché à côté de tout contenu que vous publierez. Vous pourrez le modifier après avoir créé votre compte.</string>
<string name="setup_next">Suivant</string>
<string name="setup_password_intro">Choisir un mot de passe</string>
<string name="setup_password_explanation">Votre compte Briar est enregistré chiffré sur votre appareil et non dans le nuage. Si vous oubliez votre mot de passe ou désinstallez Briar, votre compte ne peut pas être récupéré.\n\nChoisissez un mot de passe long qui sera difficile à deviner, par exemple quatre mots au hasard ou dix lettres, chiffres et symboles au hasard.</string>
<string name="dnkm_doze_title">Connexions darrière-plan</string>
<string name="dnkm_doze_intro">Pour recevoir des messages, Briar a besoin de rester connectée en arrière-plan.</string>
<string name="dnkm_doze_explanation">Pour recevoir des messages, Briar a besoin de rester connectée en arrière-plan. Veuillez désactiver les optimisations de la pile afin que Briar puisse rester connectée.</string>
<string name="dnkm_doze_button">Autoriser les connexions</string>
<string name="choose_nickname">Choisissez votre pseudonyme</string>
<string name="choose_password">Choisissez votre mot de passe</string>
<string name="confirm_password">Confirmez votre mot de passe</string>
<string name="name_too_long">Le nom est trop long</string>
<string name="password_too_weak">Le mot de passe est trop faible</string>
<string name="passwords_do_not_match">Les mots de passe ne correspondent pas</string>
<string name="create_account_button">Créer un compte</string>
<string name="more_info">Plus dinformations</string>
<string name="don_t_ask_again">Ne plus demander</string>
<string name="dnkm_huawei_protected_text">Veuillez toucher le bouton ci-dessous et vous assurer que Briar est protégée dans lécran « Applis protégées ».</string>
<string name="dnkm_huawei_protected_button">Protéger Briar</string>
<string name="dnkm_huawei_protected_help">Si Briar nest pas ajoutée à la liste des applis protégées, elle ne pourra pas fonctionner en arrière-plan.</string>
<string name="dnkm_huawei_app_launch_text">Veuillez toucher le bouton ci-dessous, ouvrir lécran « Lancement des applis » et vous assurer que « Gérer manuellement » est défini pour Briar.</string>
<string name="dnkm_huawei_app_launch_button">Ouvrez les paramètres de la pile</string>
<string name="dnkm_huawei_app_launch_help">Si « Gérer manuellement » nest pas défini pour Briar dans lécran « Lancement des applis », lappli ne pourra pas fonctionner en arrière-plan.</string>
<string name="dnkm_huawei_app_launch_error_toast">Impossible douvrir les paramètres de la pile</string>
<string name="dnkm_xiaomi_text">Pour fonctionner en arrière-plan, Briar doit être verrouillée à la liste des applis récentes.</string>
<string name="dnkm_xiaomi_button">Protéger Briar</string>
<string name="dnkm_xiaomi_help">Si Briar nest pas verrouillée à la liste des applis récentes, elle ne pourra pas fonctionner en arrière-plan.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Ouvrez la liste des applis récentes (aussi appelé sélecteur dappli)\n\n2. Balayez limage de Briar vers le bas pour afficher licône de verrou\n\n3. Si le verrou nest pas verrouillé, touchez pour le verrouiller</string>
<string name="dnkm_xiaomi_dialog_body_new">1. Ouvrez la liste des applis récentes (aussi appelé sélecteur dappli)\n\n2. Touchez et maintenez limage de Briar jusquà lapparition du verrou\n\n3. Si le verrou nest pas verrouillé, touchez pour le verrouiller</string>
<string name="dnkm_warning_dozed">%s na pas pu fonctionner en arrière-plan</string>
<!--Login-->
<string name="enter_password">Mot de passe</string>
<string name="try_again">Le mot de passe est erroné, réessayez</string>
@@ -43,12 +49,10 @@
<string name="startup_failed_service_error">Briar na pas pu lancer un composant essentiel.\n\nVeuillez mettre lappli à jour vers la version la plus récente et réessayer.</string>
<plurals name="expiry_warning">
<item quantity="one">Ceci est une version dessai de Briar. Votre compte arrivera à expiration dans %d jour et ne peut pas être renouvelé.</item>
<item quantity="many">Ceci est une version dessai de Briar. Votre compte arrivera à expiration dans %d jours et ne pourra pas être renouvelé.</item>
<item quantity="other">Ceci est une version dessai de Briar. Votre compte arrivera à expiration dans %d jours et ne pourra pas être renouvelé.</item>
</plurals>
<plurals name="old_android_expiry_warning">
<item quantity="one">Android 4 nest plus pris en charge. Briar cessera de fonctionner le %s (dans %d jour). Veuillez installer Briar sur un appareil plus récent et créer un nouveau compte.</item>
<item quantity="many">Android 4 nest plus pris en charge. Briar cessera de fonctionner le %s (dans %d jours). Veuillez installer Briar sur un appareil plus récent et créer un nouveau compte.</item>
<item quantity="other">Android 4 nest plus pris en charge. Briar cessera de fonctionner le %s (dans %d jours). Veuillez installer Briar sur un appareil plus récent et créer un nouveau compte.</item>
</plurals>
<string name="expiry_date_reached">Ce logiciel est arrivé à expiration.\nMerci de lavoir testé!</string>
@@ -111,22 +115,18 @@
<string name="ongoing_notification_text">Toucher pour ouvrir Briar.</string>
<plurals name="private_message_notification_text">
<item quantity="one">Nouveau message privé.</item>
<item quantity="many">%d nouveaux messages privés.</item>
<item quantity="other">%d nouveaux messages privés.</item>
</plurals>
<plurals name="group_message_notification_text">
<item quantity="one">Nouveau message de groupe.</item>
<item quantity="many">%d nouveaux messages de groupe.</item>
<item quantity="other">%d nouveaux messages de groupe.</item>
</plurals>
<plurals name="forum_post_notification_text">
<item quantity="one">Un nouvel article de forum.</item>
<item quantity="many">%d nouveaux articles de forum.</item>
<item quantity="other">%d nouveaux articles de forum.</item>
</plurals>
<plurals name="blog_post_notification_text">
<item quantity="one">Nouveau billet de blogue.</item>
<item quantity="many">%d nouveaux billets de blogue.</item>
<item quantity="other">%d nouveaux billets de blogue.</item>
</plurals>
<!--Misc-->
@@ -181,17 +181,14 @@
<string name="auto_delete_msg_contact_enabled">Les messages de %1$s disparaîtront après %2$s. %3$s</string>
<plurals name="duration_minutes">
<item quantity="one">%d minute</item>
<item quantity="many">%d minutes</item>
<item quantity="other">%d minutes</item>
</plurals>
<plurals name="duration_hours">
<item quantity="one">%d heure</item>
<item quantity="many">%d heures</item>
<item quantity="other">%d heures</item>
</plurals>
<plurals name="duration_days">
<item quantity="one">%d jour</item>
<item quantity="many">%d jours</item>
<item quantity="other">%d jours</item>
</plurals>
<!--The first placeholder will show a contact's name. The second placeholder at the end will add "Tap to learn more."-->
@@ -238,15 +235,17 @@
<string name="contact_added_toast">Contact ajouté : %s</string>
<string name="contact_already_exists">Le contact %s existe déjà</string>
<string name="qr_code_invalid">Le code QR est invalide</string>
<string name="qr_code_too_old">Le code QR que vous avez balayé provient dune version plus ancienne de %s.\n\nVeuillez demander à votre contact de passer à la version la plus récente et réessayer.</string>
<string name="qr_code_too_new">Le code QR que vous avez balayé provient dune version plus récente de %s.\n\nVeuillez passer à la version la plus récente et réessayer.</string>
<string name="camera_error">Erreur de lappareil photo</string>
<string name="connecting_to_device">Connexion à lappareil\u2026</string>
<string name="authenticating_with_device">Autentification avec lappareil\u2026</string>
<string name="connection_error_title">Impossible de se connecter à votre contact</string>
<string name="connection_error_feedback">Si le problème persiste, veuillez nous <a href="feedback">envoyer une rétroaction</a> pour nous aider à améliorer lappli.</string>
<!--Adding Contacts Remotely-->
<string name="add_contact_remotely_title_case">Ajouter un contact à distance</string>
<string name="add_contact_remotely_title_case">Ajouter un contact éloigné</string>
<string name="add_contact_nearby_title">Ajouter un contact à proximité</string>
<string name="add_contact_remotely_title">Ajouter un contact à distance</string>
<string name="add_contact_remotely_title">Ajouter un contact éloigné</string>
<string name="contact_link_intro">Saisissez ici le lien de votre contact</string>
<string name="contact_link_hint">Lien de votre contact</string>
<string name="paste_button">Coller</string>
@@ -282,7 +281,6 @@
<string name="step_2">2</string>
<plurals name="contact_added_notification_text">
<item quantity="one">Un nouveau contact a été ajouté</item>
<item quantity="many">%d nouveaux contacts ont été ajoutés.</item>
<item quantity="other">%d nouveaux contacts ont été ajoutés.</item>
</plurals>
<string name="offline_state">Aucune connexion à Internet</string>
@@ -303,7 +301,7 @@
<string name="pending_contact_updated_toast">Le contact en attente a été mis à jour</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Présenter vos contacts</string>
<string name="introduction_onboarding_text">Présentez vos contacts l\'un à l\'autre ainsi ils pourront se contacter via Briar.</string>
<string name="introduction_onboarding_text">Vous pouvez présenter vos contacts mutuellement, afin quils naient pas à se rencontrer en personne pour se connecter les uns aux autres avec Briar.</string>
<string name="introduction_menu_item">Faire les présentations</string>
<string name="introduction_activity_title">Sélectionner un contact </string>
<string name="introduction_not_possible">Une présentation est déjà en cours avec ces contacts. Veuillez dabord lui permettre de se terminer. Si vous ou vos contacts êtes rarement en ligne, cela peut prendre du temps.</string>
@@ -339,7 +337,6 @@
<string name="groups_created_by">Créé par %s</string>
<plurals name="messages">
<item quantity="one">%d message</item>
<item quantity="many">%d messages</item>
<item quantity="other">%d messages</item>
</plurals>
<string name="groups_group_is_empty">Ce groupe est vide</string>
@@ -373,7 +370,6 @@
<string name="groups_invitations_declined">Linvitation au groupe a été refusée</string>
<plurals name="groups_invitations_open">
<item quantity="one">%d invitation au groupe en attente</item>
<item quantity="many">%d invitations au groupe en attente</item>
<item quantity="other">%d invitations au groupe en attente</item>
</plurals>
<string name="groups_invitations_response_accepted_sent">Vous avez accepté linvitation au groupe de %s.</string>
@@ -400,7 +396,6 @@
<string name="no_posts">Aucun article</string>
<plurals name="posts">
<item quantity="one">%d article</item>
<item quantity="many">%d articles</item>
<item quantity="other">%d articles</item>
</plurals>
<string name="forum_new_message_hint">Nouvelle article</string>
@@ -438,7 +433,6 @@
<string name="shared_with">Partagé avec %1$d (%2$d en ligne)</string>
<plurals name="forums_shared">
<item quantity="one">%d forum partagé par des contacts</item>
<item quantity="many">%d forums partagés par des contacts</item>
<item quantity="other">%d forums partagés par des contacts</item>
</plurals>
<string name="nobody">Personne</string>
@@ -599,13 +593,10 @@
<string name="mailbox_setup_io_error_title">Connexion impossible</string>
<string name="mailbox_setup_io_error_description">Assurez-vous que les deux appareils sont connectés à Internet et réessayez.</string>
<string name="mailbox_setup_assertion_error_title">Erreur de la Boîte de courriel</string>
<string name="mailbox_setup_assertion_error_description">Merci de transmettre vos retours (données anonymes) via l\'application Briar si le problème persiste.</string>
<string name="mailbox_setup_camera_error_description">Pas d\'accès à l\'appareil photo. réessayez après avoir redémarré l\'appareil.</string>
<string name="mailbox_setup_paired_title">Connecté</string>
<string name="mailbox_setup_paired_description">Votre Boîte de courriel a été reliée avec succès à Briar.\n
\nGarder votre boîte de courriel connectée à lalimentation et au Wi-Fi afin quelle soit toujours en ligne.</string>
<string name="tor_offline_title">Hors ligne</string>
<string name="tor_offline_button_check">Vérifiez les paramètres de connexion.</string>
<string name="mailbox_status_title">État de la Boîte de courriel</string>
<string name="mailbox_status_connected_title">La Boîte de courriel est en cours dexécution</string>
<!--Example for string substitution: Last connection: 3min ago-->
@@ -713,8 +704,6 @@ copies des messages que vous envoyez.
<string name="hotspot_scanning_a_qr_code">balayant un code QR</string>
<!--Wi-Fi setup-->
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
<string name="hotspot_manual_wifi_ssid">Nom du réseau</string>
<string name="hotspot_no_peers_connected">Aucun appareil connecté</string>
<!--Download link-->
<!--The %s placeholder will be replaced with the translation of 'hotspot_scanning_a_qr_code'-->
<!--e.g. Download Briar 1.2.20-->

View File

@@ -1,31 +1,36 @@
<?xml version='1.0' encoding='UTF-8'?>
<resources xmlns:tools="http://schemas.android.com/tools">
<!--Setup-->
<string name="setup_title">Velkomin í Briar</string>
<string name="setup_name_explanation">Stuttnefnið þitt birtist við hlið alls þess efnis sem þú sendir inn. Þú getur ekki breytt því eftir að þú hefur skráð notandaaðganginn þinn.</string>
<string name="setup_next">Næsta</string>
<string name="setup_password_intro">Veldu lykilorð</string>
<string name="setup_password_explanation">Notandaaðgangur þinn í Briar er geymdur dulritaður á tækinu þínu, ekki í tölvuskýi. Ef þú gleymir lykilorðinu þínu eða fjarlægir Briar, þá er engin leið til að endurheimta notandaaðganginn þinn.\n\nVeldu langt lykilorð sem erfitt er að giska á, svo sem eins og fjögur orð af handahófi, eða slembna samsetningu tíu bósktafa, tölustafa og tákna.</string>
<string name="dnkm_doze_intro">Til að taka á móti skilaboðum þarf Briar að haldast tengt í bakgrunni.</string>
<string name="dnkm_doze_explanation">Til að taka á móti skilaboðum þarf Briar að haldast tengt í bakgrunni. Gerðu orkusparnaðarferli óvirk svo Briar geti haldið tengingum.</string>
<string name="choose_nickname">Veldu þér stuttnefni</string>
<string name="choose_password">Veldu þér lykilorð</string>
<string name="confirm_password">Staðfestu lykilorðið</string>
<string name="name_too_long">Nafnið er of langt</string>
<string name="password_too_weak">Lykilorðið er of veikt</string>
<string name="passwords_do_not_match">Lykilorðin stemma ekki</string>
<string name="create_account_button">Búa til notandaaðgang</string>
<string name="more_info">Nánari upplýsingar</string>
<string name="don_t_ask_again">Ekki spyrja aftur</string>
<string name="dnkm_huawei_protected_text">Ýttu á hnappinn hér fyrir neðan og gakktu úr skugga um að Briar sé varið á skjánum \"Varin forrit\".</string>
<string name="dnkm_huawei_protected_button">Vernda Briar</string>
<string name="dnkm_huawei_protected_help">Ef Briar er ekki bætt á listann yfir varin forrit, getur það ekki keyrt í bakgrunni.</string>
<string name="dnkm_huawei_app_launch_text">Ýttu á hnappinn hér fyrir neðan, opnaðu \"Ræsing forrits\" skjáinn og gakktu úr skugga um að Briar sé stillt á \"Stýra handvirkt\".</string>
<string name="dnkm_huawei_app_launch_help">Ef Briar er ekki stillt á \"Stýra handvirkt\" í \"Ræsing forrits\" skjánum, mun það ekki geta keyrt í bakgrunni.</string>
<string name="dnkm_xiaomi_text">Til að keyra í bakgrunni þarf Briar að vera læst við listann yfir nýleg forrit.</string>
<string name="dnkm_xiaomi_button">Vernda Briar</string>
<string name="dnkm_xiaomi_help">Ef Briar er ekki læst við listann yfir nýleg forrit, mun það ekki geta keyrt í bakgrunni.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Opnaðu listann yfir nýleg forrit (einnig kallað forritaskiptir)\n\n2. Strjúktu niður myndina af Briar til að birta hengilástáknið\n\n3. Ef hengilásinn er ekki læstur, ýttu á hann til að læsa</string>
<resources>
<!--Setup-->
<string name="setup_title">Velkomin í Briar</string>
<string name="setup_name_explanation">Stuttnefnið þitt birtist við hlið alls þess efnis sem þú sendir inn. Þú getur ekki breytt því eftir að þú hefur skráð notandaaðganginn þinn.</string>
<string name="setup_next">Næsta</string>
<string name="setup_password_intro">Veldu lykilorð</string>
<string name="setup_password_explanation">Notandaaðgangur þinn í Briar er geymdur dulritaður á tækinu þínu, ekki í tölvuskýi. Ef þú gleymir lykilorðinu þínu eða fjarlægir Briar, þá er engin leið til að endurheimta notandaaðganginn þinn.\n\nVeldu langt lykilorð sem erfitt er að giska á, svo sem eins og fjögur orð af handahófi, eða slembna samsetningu tíu bósktafa, tölustafa og tákna.</string>
<string name="dnkm_doze_title">Bakgrunnstengingar</string>
<string name="dnkm_doze_intro">Til að taka á móti skilaboðum þarf Briar að haldast tengt í bakgrunni.</string>
<string name="dnkm_doze_explanation">Til að taka á móti skilaboðum þarf Briar að haldast tengt í bakgrunni. Gerðu orkusparnaðarferli óvirk svo Briar geti haldið tengingum.</string>
<string name="dnkm_doze_button">Leyfa tengingar</string>
<string name="choose_nickname">Veldu þér stuttnefni</string>
<string name="choose_password">Veldu þér lykilorð</string>
<string name="confirm_password">Staðfestu lykilorðið</string>
<string name="name_too_long">Nafnið er of langt</string>
<string name="password_too_weak">Lykilorðið er of veikt</string>
<string name="passwords_do_not_match">Lykilorðin stemma ekki</string>
<string name="create_account_button">Búa til notandaaðgang</string>
<string name="more_info">Nánari upplýsingar</string>
<string name="don_t_ask_again">Ekki spyrja aftur</string>
<string name="dnkm_huawei_protected_text">Ýttu á hnappinn hér fyrir neðan og gakktu úr skugga um að Briar sé varið á skjánum \"Varin forrit\".</string>
<string name="dnkm_huawei_protected_button">Vernda Briar</string>
<string name="dnkm_huawei_protected_help">Ef Briar er ekki bætt á listann yfir varin forrit, getur það ekki keyrt í bakgrunni.</string>
<string name="dnkm_huawei_app_launch_text">Ýttu á hnappinn hér fyrir neðan, opnaðu \"Ræsing forrits\" skjáinn og gakktu úr skugga um að Briar sé stillt á \"Stýra handvirkt\".</string>
<string name="dnkm_huawei_app_launch_button">Opna rafhlöðustillingar</string>
<string name="dnkm_huawei_app_launch_help">Ef Briar er ekki stillt á \"Stýra handvirkt\" í \"Ræsing forrits\" skjánum, mun það ekki geta keyrt í bakgrunni.</string>
<string name="dnkm_xiaomi_text">Til að keyra í bakgrunni þarf Briar að vera læst við listann yfir nýleg forrit.</string>
<string name="dnkm_xiaomi_button">Vernda Briar</string>
<string name="dnkm_xiaomi_help">Ef Briar er ekki læst við listann yfir nýleg forrit, mun það ekki geta keyrt í bakgrunni.</string>
<string name="dnkm_xiaomi_dialog_body_old">1. Opnaðu listann yfir nýleg forrit (einnig kallað forritaskiptir)\n\n2. Strjúktu niður myndina af Briar til að birta hengilástáknið\n\n3. Ef hengilásinn er ekki læstur, ýttu á hann til að læsa</string>
<string name="dnkm_xiaomi_dialog_body_new">1. Opnaðu listann yfir nýleg forrit (einnig kallað forritaskiptir)\n\n2. Ýttu og haltu niðri á myndina af Briar til að birta hengiláshnappinn\n\n3. Ef hengilásinn er ekki læstur, ýttu á hann til að læsa</string>
<string name="dnkm_warning_dozed">%s gat ekki keyrt í bakgrunni</string>
<!--Login-->
<string name="enter_password">Lykilorð</string>
<string name="try_again">Rangt lykilorð, reyndu aftur</string>
@@ -229,6 +234,8 @@
<string name="contact_added_toast">Tengilið bætt við: %s</string>
<string name="contact_already_exists">Tengiliðurinn %s er þegar til</string>
<string name="qr_code_invalid">QR-kóðinn er ógildur</string>
<string name="qr_code_too_old">QR-kóðinn sem þú skannaðir kemur frá eldri útgáfu af %s.\n\nBiddu tengiliðinn þinn um að uppfæra í nýjustu útgáfuna og prófaðu síðan aftur.</string>
<string name="qr_code_too_new">QR-kóðinn sem þú skannaðir kemur frá nýrri útgáfu af %s.\n\nUppfærðu í nýjustu útgáfuna og prófaðu síðan aftur.</string>
<string name="camera_error">Villa í myndavél</string>
<string name="connecting_to_device">Tengist við tæki\u2026</string>
<string name="authenticating_with_device">Auðkenni við tæki\u2026</string>
@@ -293,6 +300,7 @@
<string name="pending_contact_updated_toast">Tengiliður í bið uppfærður</string>
<!--Introductions-->
<string name="introduction_onboarding_title">Kynntu tengiliðina þína</string>
<string name="introduction_onboarding_text">Þú getur kynnt tengiliðina þína fyrir hver öðrum, svo að þeir þurfi ekki að hittast í eigin persónu til að tengjast í gegnum Briar.</string>
<string name="introduction_menu_item">Útbúa kynningu</string>
<string name="introduction_activity_title">Veldu tengilið</string>
<string name="introduction_not_possible">Þú ert þegar með eina kynningu í vinnslu gagnvart þessum tengiliðum. Leyfðu því ferli að ljúka fyrst. Ef þú eða tengiliðirnir þínir eruð sjaldan á netinu, gæti þetta tekið svolítinn tíma.</string>
@@ -596,13 +604,10 @@
<string name="tor_offline_button_check">Athugaðu tengistillingarnar</string>
<string name="mailbox_status_title">Staða pósthólfs</string>
<string name="mailbox_status_connected_title">Pósthólf er í gangi</string>
<string name="mailbox_status_failure_title">Pósthólf er ekki tiltækt</string>
<string name="mailbox_status_check_button">Athugaðu tenginguna</string>
<!--Example for string substitution: Last connection: 3min ago-->
<string name="mailbox_status_connected_info">Síðasta tenging: %s</string>
<!--Indicates that there never was a connection to the mailbox. Last connection: Never-->
<string name="mailbox_status_connected_never">Aldrei</string>
<string name="mailbox_status_unlink_button">Aftengja</string>
<!--Conversation Settings-->
<string name="disappearing_messages_title">Sjálfeyðandi skilaboð</string>
<string name="disappearing_messages_explanation_long">Ef kveikt er á þessari stillingu munu ný
@@ -718,6 +723,8 @@
<string name="hotspot_manual_site_address">Vistfang (URL)</string>
<string name="hotspot_qr_site">Síminn þinn er að reka Wi-Fi aðgangsstað. Fólk sem er tengt aðgangsstaðnum getur sótt Briar með því að skanna þennan QR-kóða.</string>
<!--e.g. Download Briar 1.2.20-->
<string name="website_download_title">Sækja %s</string>
<string name="website_download_intro">Einhver í nágrenninu hefur deilt %s með þér.</string>
<string name="website_download_outro">Eftir að niðurhalinu er lokið, skaltu opna sóttu skrána og setja hana upp.</string>
<string name="website_troubleshooting_title">Lausn á vandamálum</string>
<string name="website_troubleshooting_1">Ef þú getur ekki sótt forritið, ættirðu að prófa það með öðrum vafra.</string>

Some files were not shown because too many files have changed in this diff Show More