cache last used service record in DB
This commit is contained in:
parent
807078b24f
commit
2e5e2ff6fe
|
@ -2,7 +2,7 @@
|
||||||
"formatVersion": 1,
|
"formatVersion": 1,
|
||||||
"database": {
|
"database": {
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"identityHash": "bc04f3d0c58f7e50f5c7973a7a06c9eb",
|
"identityHash": "b5e8a59bbd86e133c0bc2edd303ad2a0",
|
||||||
"entities": [
|
"entities": [
|
||||||
{
|
{
|
||||||
"tableName": "account",
|
"tableName": "account",
|
||||||
|
@ -2432,12 +2432,103 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"tableName": "service_record_cache",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `domain` TEXT NOT NULL, `ip` BLOB, `hostname` TEXT, `port` INTEGER NOT NULL, `directTls` INTEGER NOT NULL, `priority` INTEGER NOT NULL, `authenticated` INTEGER NOT NULL, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "accountId",
|
||||||
|
"columnName": "accountId",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "domain",
|
||||||
|
"columnName": "domain",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceRecord.ip",
|
||||||
|
"columnName": "ip",
|
||||||
|
"affinity": "BLOB",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceRecord.hostname",
|
||||||
|
"columnName": "hostname",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceRecord.port",
|
||||||
|
"columnName": "port",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceRecord.directTls",
|
||||||
|
"columnName": "directTls",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceRecord.priority",
|
||||||
|
"columnName": "priority",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "serviceRecord.authenticated",
|
||||||
|
"columnName": "authenticated",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"autoGenerate": true,
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"indices": [
|
||||||
|
{
|
||||||
|
"name": "index_service_record_cache_accountId_domain",
|
||||||
|
"unique": true,
|
||||||
|
"columnNames": [
|
||||||
|
"accountId",
|
||||||
|
"domain"
|
||||||
|
],
|
||||||
|
"orders": [],
|
||||||
|
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_service_record_cache_accountId_domain` ON `${TABLE_NAME}` (`accountId`, `domain`)"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"foreignKeys": [
|
||||||
|
{
|
||||||
|
"table": "account",
|
||||||
|
"onDelete": "CASCADE",
|
||||||
|
"onUpdate": "NO ACTION",
|
||||||
|
"columns": [
|
||||||
|
"accountId"
|
||||||
|
],
|
||||||
|
"referencedColumns": [
|
||||||
|
"id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"views": [],
|
"views": [],
|
||||||
"setupQueries": [
|
"setupQueries": [
|
||||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'bc04f3d0c58f7e50f5c7973a7a06c9eb')"
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b5e8a59bbd86e133c0bc2edd303ad2a0')"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -17,6 +17,7 @@ import im.conversations.android.database.dao.MessageDao;
|
||||||
import im.conversations.android.database.dao.NickDao;
|
import im.conversations.android.database.dao.NickDao;
|
||||||
import im.conversations.android.database.dao.PresenceDao;
|
import im.conversations.android.database.dao.PresenceDao;
|
||||||
import im.conversations.android.database.dao.RosterDao;
|
import im.conversations.android.database.dao.RosterDao;
|
||||||
|
import im.conversations.android.database.dao.ServiceRecordDao;
|
||||||
import im.conversations.android.database.entity.AccountEntity;
|
import im.conversations.android.database.entity.AccountEntity;
|
||||||
import im.conversations.android.database.entity.AvatarAdditionalEntity;
|
import im.conversations.android.database.entity.AvatarAdditionalEntity;
|
||||||
import im.conversations.android.database.entity.AvatarEntity;
|
import im.conversations.android.database.entity.AvatarEntity;
|
||||||
|
@ -49,6 +50,7 @@ import im.conversations.android.database.entity.NickEntity;
|
||||||
import im.conversations.android.database.entity.PresenceEntity;
|
import im.conversations.android.database.entity.PresenceEntity;
|
||||||
import im.conversations.android.database.entity.RosterItemEntity;
|
import im.conversations.android.database.entity.RosterItemEntity;
|
||||||
import im.conversations.android.database.entity.RosterItemGroupEntity;
|
import im.conversations.android.database.entity.RosterItemGroupEntity;
|
||||||
|
import im.conversations.android.database.entity.ServiceRecordCacheEntity;
|
||||||
|
|
||||||
@Database(
|
@Database(
|
||||||
entities = {
|
entities = {
|
||||||
|
@ -83,7 +85,8 @@ import im.conversations.android.database.entity.RosterItemGroupEntity;
|
||||||
PresenceEntity.class,
|
PresenceEntity.class,
|
||||||
MessageReactionEntity.class,
|
MessageReactionEntity.class,
|
||||||
RosterItemEntity.class,
|
RosterItemEntity.class,
|
||||||
RosterItemGroupEntity.class
|
RosterItemGroupEntity.class,
|
||||||
|
ServiceRecordCacheEntity.class
|
||||||
},
|
},
|
||||||
version = 1)
|
version = 1)
|
||||||
@TypeConverters(Converters.class)
|
@TypeConverters(Converters.class)
|
||||||
|
@ -130,4 +133,6 @@ public abstract class ConversationsDatabase extends RoomDatabase {
|
||||||
public abstract PresenceDao presenceDao();
|
public abstract PresenceDao presenceDao();
|
||||||
|
|
||||||
public abstract RosterDao rosterDao();
|
public abstract RosterDao rosterDao();
|
||||||
|
|
||||||
|
public abstract ServiceRecordDao serviceRecordDao();
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ package im.conversations.android.database;
|
||||||
|
|
||||||
import androidx.room.TypeConverter;
|
import androidx.room.TypeConverter;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import de.measite.minidns.DNSName;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import org.jxmpp.jid.BareJid;
|
import org.jxmpp.jid.BareJid;
|
||||||
import org.jxmpp.jid.Jid;
|
import org.jxmpp.jid.Jid;
|
||||||
|
@ -145,4 +148,31 @@ public final class Converters {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static String fromDNSName(final DNSName dnsName) {
|
||||||
|
return dnsName == null ? null : dnsName.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static DNSName toDNSName(final String dnsName) {
|
||||||
|
return dnsName == null ? null : DNSName.from(dnsName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static byte[] fromInetAddress(final InetAddress inetAddress) {
|
||||||
|
return inetAddress == null ? null : inetAddress.getAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
@TypeConverter
|
||||||
|
public static InetAddress toInetAddress(final byte[] address) {
|
||||||
|
if (address == null || address.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return InetAddress.getByAddress(address);
|
||||||
|
} catch (final UnknownHostException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package im.conversations.android.database.dao;
|
||||||
|
|
||||||
|
import androidx.room.Dao;
|
||||||
|
import androidx.room.Insert;
|
||||||
|
import androidx.room.OnConflictStrategy;
|
||||||
|
import androidx.room.Query;
|
||||||
|
import im.conversations.android.database.entity.ServiceRecordCacheEntity;
|
||||||
|
import im.conversations.android.database.model.Account;
|
||||||
|
import im.conversations.android.dns.ServiceRecord;
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
public abstract class ServiceRecordDao {
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
protected abstract void insert(ServiceRecordCacheEntity entity);
|
||||||
|
|
||||||
|
public void insert(final Account account, final ServiceRecord serviceRecord) {
|
||||||
|
insert(
|
||||||
|
ServiceRecordCacheEntity.of(
|
||||||
|
account, account.address.getDomain().toString(), serviceRecord));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"SELECT ip,hostname,port,directTls,priority,authenticated FROM service_record_cache"
|
||||||
|
+ " WHERE accountId=:account AND domain=:domain LIMIT 1")
|
||||||
|
protected abstract ServiceRecord getCachedServiceRecord(
|
||||||
|
final long account, final String domain);
|
||||||
|
|
||||||
|
public ServiceRecord getCachedServiceRecord(final Account account) {
|
||||||
|
return getCachedServiceRecord(account.id, account.address.getDomain().toString());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package im.conversations.android.database.entity;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.room.Embedded;
|
||||||
|
import androidx.room.Entity;
|
||||||
|
import androidx.room.ForeignKey;
|
||||||
|
import androidx.room.Index;
|
||||||
|
import androidx.room.PrimaryKey;
|
||||||
|
import im.conversations.android.database.model.Account;
|
||||||
|
import im.conversations.android.dns.ServiceRecord;
|
||||||
|
|
||||||
|
@Entity(
|
||||||
|
tableName = "service_record_cache",
|
||||||
|
foreignKeys =
|
||||||
|
@ForeignKey(
|
||||||
|
entity = AccountEntity.class,
|
||||||
|
parentColumns = {"id"},
|
||||||
|
childColumns = {"accountId"},
|
||||||
|
onDelete = ForeignKey.CASCADE),
|
||||||
|
indices = {
|
||||||
|
@Index(
|
||||||
|
value = {"accountId", "domain"},
|
||||||
|
unique = true)
|
||||||
|
})
|
||||||
|
public class ServiceRecordCacheEntity {
|
||||||
|
|
||||||
|
@PrimaryKey(autoGenerate = true)
|
||||||
|
public Long id;
|
||||||
|
|
||||||
|
@NonNull public Long accountId;
|
||||||
|
|
||||||
|
@NonNull public String domain;
|
||||||
|
|
||||||
|
@Embedded @NonNull public ServiceRecord serviceRecord;
|
||||||
|
|
||||||
|
public static ServiceRecordCacheEntity of(
|
||||||
|
final Account account, final String domain, final ServiceRecord serviceRecord) {
|
||||||
|
final var entity = new ServiceRecordCacheEntity();
|
||||||
|
entity.accountId = account.id;
|
||||||
|
entity.domain = domain;
|
||||||
|
entity.serviceRecord = serviceRecord;
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
|
@ -41,6 +41,7 @@ import org.jxmpp.jid.DomainJid;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@SuppressWarnings("UnstableApiUsage")
|
||||||
public class Resolver {
|
public class Resolver {
|
||||||
|
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(Resolver.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(Resolver.class);
|
||||||
|
|
|
@ -59,6 +59,10 @@ public class ServiceRecord implements Comparable<ServiceRecord> {
|
||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPriority() {
|
||||||
|
return this.priority;
|
||||||
|
}
|
||||||
|
|
||||||
public DNSName getHostname() {
|
public DNSName getHostname() {
|
||||||
return hostname;
|
return hostname;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import androidx.annotation.Nullable;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ClassToInstanceMap;
|
import com.google.common.collect.ClassToInstanceMap;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
@ -338,22 +339,30 @@ public class XmppConnection implements Runnable {
|
||||||
LOGGER.warn("Resolver results were empty");
|
LOGGER.warn("Resolver results were empty");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
final List<ServiceRecord> resultsWithBackup;
|
||||||
final ServiceRecord storedBackupResult;
|
final ServiceRecord storedBackupResult;
|
||||||
if (connection != null) {
|
if (connection != null) {
|
||||||
storedBackupResult = null;
|
storedBackupResult = null;
|
||||||
|
resultsWithBackup = results;
|
||||||
} else {
|
} else {
|
||||||
// TODO fix resolver result caching
|
|
||||||
storedBackupResult =
|
storedBackupResult =
|
||||||
null; // context.databaseBackend.findResolverResult(domain);
|
ConversationsDatabase.getInstance(context)
|
||||||
|
.serviceRecordDao()
|
||||||
|
.getCachedServiceRecord(account);
|
||||||
if (storedBackupResult != null && !results.contains(storedBackupResult)) {
|
if (storedBackupResult != null && !results.contains(storedBackupResult)) {
|
||||||
results.add(storedBackupResult);
|
resultsWithBackup =
|
||||||
|
new ImmutableList.Builder<ServiceRecord>()
|
||||||
|
.addAll(results)
|
||||||
|
.add(storedBackupResult)
|
||||||
|
.build();
|
||||||
LOGGER.debug(
|
LOGGER.debug(
|
||||||
account.address
|
"loaded backup resolver result from db {}", storedBackupResult);
|
||||||
+ ": loaded backup resolver result from db: "
|
} else {
|
||||||
+ storedBackupResult);
|
resultsWithBackup = results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (Iterator<ServiceRecord> iterator = results.iterator(); iterator.hasNext(); ) {
|
for (final Iterator<ServiceRecord> iterator = resultsWithBackup.iterator();
|
||||||
|
iterator.hasNext(); ) {
|
||||||
final ServiceRecord result = iterator.next();
|
final ServiceRecord result = iterator.next();
|
||||||
if (Thread.currentThread().isInterrupted()) {
|
if (Thread.currentThread().isInterrupted()) {
|
||||||
LOGGER.debug(account.address + ": Thread was interrupted");
|
LOGGER.debug(account.address + ": Thread was interrupted");
|
||||||
|
@ -362,9 +371,8 @@ public class XmppConnection implements Runnable {
|
||||||
try {
|
try {
|
||||||
// if tls is true, encryption is implied and must not be started
|
// if tls is true, encryption is implied and must not be started
|
||||||
this.encryptionEnabled = result.isDirectTls();
|
this.encryptionEnabled = result.isDirectTls();
|
||||||
verifiedHostname =
|
this.verifiedHostname =
|
||||||
result.isAuthenticated() ? result.getHostname().toString() : null;
|
result.isAuthenticated() ? result.getHostname().toString() : null;
|
||||||
LOGGER.debug("verified hostname " + verifiedHostname);
|
|
||||||
final InetSocketAddress addr;
|
final InetSocketAddress addr;
|
||||||
if (result.getIp() != null) {
|
if (result.getIp() != null) {
|
||||||
addr = new InetSocketAddress(result.getIp(), result.getPort());
|
addr = new InetSocketAddress(result.getIp(), result.getPort());
|
||||||
|
@ -403,12 +411,11 @@ public class XmppConnection implements Runnable {
|
||||||
|
|
||||||
localSocket.setSoTimeout(ConnectionPool.SOCKET_TIMEOUT * 1000);
|
localSocket.setSoTimeout(ConnectionPool.SOCKET_TIMEOUT * 1000);
|
||||||
if (startXmpp(localSocket)) {
|
if (startXmpp(localSocket)) {
|
||||||
localSocket.setSoTimeout(
|
localSocket.setSoTimeout(0);
|
||||||
0); // reset to 0; once the connection is established we don’t
|
|
||||||
// want this
|
|
||||||
if (connection == null && !result.equals(storedBackupResult)) {
|
if (connection == null && !result.equals(storedBackupResult)) {
|
||||||
// TODO store resolver result
|
ConversationsDatabase.getInstance(context)
|
||||||
// context.databaseBackend.saveResolverResult(domain, result);
|
.serviceRecordDao()
|
||||||
|
.insert(account, result);
|
||||||
}
|
}
|
||||||
break; // successfully connected to server that speaks xmpp
|
break; // successfully connected to server that speaks xmpp
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue