This commit is contained in:
Paul Schaub 2020-05-11 16:31:07 +02:00
parent f58ad142cc
commit b56b451a4b
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
16 changed files with 142 additions and 188 deletions

View File

@ -10,6 +10,7 @@ import org.mercury_im.messenger.di.component.DaggerAppComponent;
import org.mercury_im.messenger.di.module.AppModule;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.service.MercuryConnectionService;
import org.mercury_im.messenger.xmpp.CsiManager;
import java.util.List;
@ -56,7 +57,7 @@ public class MercuryImApplication extends Application {
}
private void setupClientStateIndication() {
clientStateHandler.addClientStateListener(messenger);
clientStateHandler.addClientStateListener(new CsiManager(messenger.getConnectionManager()));
registerActivityLifecycleCallbacks(clientStateHandler);
}

View File

@ -116,7 +116,7 @@ public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter<AccountsRe
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return oldItems.get(oldItemPosition).getAccount().getId() == newItems.get(newItemPosition).getAccount().getId();
return oldItems.get(oldItemPosition).getAccountId().equals(newItems.get(newItemPosition).getAccountId());
}
@Override

View File

@ -17,12 +17,15 @@ import org.mercury_im.messenger.Messenger;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.entity.IAccount;
import org.mercury_im.messenger.xmpp.MercuryConnection;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
@ -86,6 +89,14 @@ public class LoginViewModel extends AndroidViewModel {
Account account = new IAccount();
account.setAddress(username.asUnescapedString());
account.setPassword(password);
MercuryConnection connection = messenger.getConnectionManager().createConnection(account);
disposable.add(Completable.fromAction(() -> {
connection.connect().login();
})
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe()
);
disposable.add(messenger.addAccount()
.execute(account)
.subscribeOn(Schedulers.newThread())

View File

@ -1,11 +1,6 @@
package org.mercury_im.messenger;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smackx.csi.ClientStateIndicationManager;
import org.mercury_im.messenger.data.repository.Repositories;
import org.mercury_im.messenger.usecase.AddAccount;
import org.mercury_im.messenger.usecase.RosterStoreBinder;
import org.mercury_im.messenger.xmpp.MercuryConnection;
import org.mercury_im.messenger.xmpp.MercuryConnectionManager;
import java.util.logging.Level;
@ -18,7 +13,7 @@ import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
@Singleton
public class Messenger implements ClientStateListener {
public class Messenger {
public static final String TAG = "MercuryIM";
private static final Logger LOGGER = Logger.getLogger(Messenger.class.getName());
@ -46,41 +41,4 @@ public class Messenger implements ClientStateListener {
.subscribe(connectionManager::registerConnections));
}
public AddAccount addAccount() {
return new AddAccount(repositories.getAccountRepository(), this.connectionManager);
}
// CSI
@Override
public void onClientInForeground() {
LOGGER.log(Level.INFO, "CSI: active");
for (MercuryConnection connection : connectionManager.getConnections()) {
tryCsiActive(connection);
}
}
@Override
public void onClientInBackground() {
LOGGER.log(Level.INFO, "CSI: inactive");
for (MercuryConnection connection : connectionManager.getConnections()) {
tryCsiInactive(connection);
}
}
private void tryCsiActive(MercuryConnection connection) {
try {
ClientStateIndicationManager.active(connection.getConnection());
} catch (SmackException.NotConnectedException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Sending CSI state 'active' failed.", e);
}
}
private void tryCsiInactive(MercuryConnection connection) {
try {
ClientStateIndicationManager.inactive(connection.getConnection());
} catch (SmackException.NotConnectedException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Sending CSI state 'inactive' failed.", e);
}
}
}

View File

@ -16,6 +16,7 @@ import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
@ -28,21 +29,21 @@ public class MercuryRosterStore implements RosterStore {
private final PeerRepository peerRepository;
private final AccountRepository accountRepository;
private Account account;
private UUID accountId;
private final CompositeDisposable disposable = new CompositeDisposable();
private final Map<String, RosterPacket.Item> itemMap = new HashMap<>();
private String rosterVersion;
public MercuryRosterStore(Account account, PeerRepository rosterRepository, AccountRepository accountRepository) {
this.account = account;
public MercuryRosterStore(UUID accountId, PeerRepository rosterRepository, AccountRepository accountRepository) {
this.accountId = accountId;
this.peerRepository = rosterRepository;
this.accountRepository = accountRepository;
LOGGER.log(Level.INFO, "Construct Roster Store for " + account.getId());
LOGGER.log(Level.INFO, "Construct Roster Store for " + accountId);
}
public void subscribe() {
disposable.add(peerRepository.observeAllContactsOfAccount(account)
disposable.add(peerRepository.observeAllContactsOfAccount(accountId)
.observeOn(Schedulers.computation())
.subscribe(contactsList -> {
itemMap.clear();
@ -96,7 +97,7 @@ public class MercuryRosterStore implements RosterStore {
}
private void writeEntryToDatabase(RosterPacket.Item item) {
disposable.add(peerRepository.getOrCreatePeer(account, item.getJid().asUnescapedString())
disposable.add(peerRepository.getOrCreatePeer(accountId, item.getJid().asUnescapedString())
.map(peer -> toEntity(item, peer))
.flatMap(peerRepository::upsertPeer)
.subscribe(
@ -139,7 +140,7 @@ public class MercuryRosterStore implements RosterStore {
public boolean removeEntry(Jid bareJid, String version) {
LOGGER.log(Level.INFO, "Remove entry " + bareJid.toString());
disposable.add(peerRepository.deletePeer(account.getId(), bareJid.asEntityBareJidOrThrow().asEntityBareJidString())
disposable.add(peerRepository.deletePeer(accountId, bareJid.asEntityBareJidOrThrow().asEntityBareJidString())
.subscribe(
() -> LOGGER.log(Level.FINE, "Deletion of contact " + bareJid.toString() + " successful"),
error -> LOGGER.log(Level.WARNING, "An error occurred deleting contact " + bareJid.toString(), error)
@ -185,7 +186,7 @@ public class MercuryRosterStore implements RosterStore {
}
public Peer toEntity(RosterPacket.Item item, Peer peer) {
peer.setAccount(account);
peer.setAccount(accountRepository.getAccount(accountId).blockingGet());
peer.setAddress(item.getJid().asEntityBareJidOrThrow().asEntityBareJidString());
peer.setName(item.getName());
if (item.getItemType() != null) {

View File

@ -1,82 +0,0 @@
package org.mercury_im.messenger.usecase;
import org.mercury_im.messenger.data.repository.AccountRepository;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.xmpp.MercuryConnection;
import org.mercury_im.messenger.xmpp.MercuryConnectionManager;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.reactivex.Completable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
public class AddAccount {
private static final Logger LOGGER = Logger.getLogger(AddAccount.class.getName());
private final AccountRepository accountRepository;
private final MercuryConnectionManager connectionManager;
private final CompositeDisposable disposable = new CompositeDisposable();
public AddAccount(AccountRepository accountRepository, MercuryConnectionManager connectionManager) {
this.accountRepository = accountRepository;
this.connectionManager = connectionManager;
}
public Completable execute(Account account) {
return loginAndStoreAccountIfSuccessful(account);
}
private Completable loginAndStoreAccountIfSuccessful(Account account) {
LOGGER.log(Level.INFO, "loginAndStoreIfSuccessful");
return logIntoAccount(account).flatMap(connection ->
insertEnabledAccountIntoDatabase(account).flatMap(insertedAccount ->
updateAccountIdInConnectionSingle(insertedAccount, connection)))
.map(this::addConnectionToMessenger)
.ignoreElement();
}
private Single<MercuryConnection> logIntoAccount(Account account) {
return getOrCreateConnectionSingle(account)
.doAfterSuccess(con -> LogIntoAccount.with(con).executeAndPossiblyThrow())
.subscribeOn(Schedulers.io());
}
private Single<MercuryConnection> getOrCreateConnectionSingle(Account account) {
return Single.fromCallable(() -> getOrCreateConnection(account));
}
private MercuryConnection getOrCreateConnection(Account account) {
MercuryConnection connection = connectionManager.getConnection(account);
if (connection == null) {
connection = new MercuryConnection(accountRepository, account);
}
return connection;
}
private Single<Account> insertEnabledAccountIntoDatabase(Account account) {
account.setEnabled(true);
return accountRepository.upsertAccount(account);
}
private Completable addConnectionToMessenger(MercuryConnection connection) {
return Completable.fromAction(() -> connectionManager.registerConnection(connection));
}
private Single<MercuryConnection> updateAccountIdInConnectionSingle(Account account, MercuryConnection connection) {
return Single.fromCallable(() -> {
updateAccountIdInConnection(account, connection);
return connection;
});
}
private void updateAccountIdInConnection(Account account, MercuryConnection connection) {
if (connection != null) {
connection.getAccount().setId(account.getId());
}
}
}

View File

@ -1,17 +0,0 @@
package org.mercury_im.messenger.usecase;
import io.reactivex.Completable;
public interface AddAccountUseCase {
AddAccountTask create();
Completable execute(AddAccountTask task);
interface AddAccountTask {
void setAddress(String address);
void setPassword(String password);
}
}

View File

@ -50,14 +50,14 @@ public class LogIntoAccount {
doAuthenticateIfNecessary();
return ConnectionResult.success;
} catch (SASLErrorException e) {
LOGGER.log(Level.WARNING, "SASL Error while connecting to account " + connection.getAccount().getAddress(), e);
LOGGER.log(Level.WARNING, "SASL Error while connecting to account " + connection.getAccountId(), e);
return ConnectionResult.credential_error;
} catch (SmackException.ConnectionException e) {
LOGGER.log(Level.WARNING, "Connectivity error while connecting to account " + connection.getAccount().getAddress(), e);
LOGGER.log(Level.WARNING, "Connectivity error while connecting to account " + connection.getAccountId(), e);
return ConnectionResult.server_error;
}
catch (IOException | XMPPException | SmackException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Error connecting to account " + connection.getAccount().getAddress(), e);
LOGGER.log(Level.WARNING, "Error connecting to account " + connection.getAccountId(), e);
return ConnectionResult.other_error;
}
}

View File

@ -7,6 +7,8 @@ import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.store.MercuryRosterStore;
import org.mercury_im.messenger.xmpp.MercuryConnection;
import java.util.UUID;
import javax.inject.Inject;
public class RosterStoreBinder {
@ -22,12 +24,12 @@ public class RosterStoreBinder {
public void setRosterStoreOn(MercuryConnection connection) {
MercuryRosterStore store =
createRosterStore(connection.getAccount(), accountRepository, peerRepository);
createRosterStore(connection.getAccountId(), accountRepository, peerRepository);
Roster roster = Roster.getInstanceFor(connection.getConnection());
roster.setRosterStore(store);
}
private MercuryRosterStore createRosterStore(Account account, AccountRepository accountRepository, PeerRepository peerRepository) {
return new MercuryRosterStore(account, peerRepository, accountRepository);
private MercuryRosterStore createRosterStore(UUID accountId, AccountRepository accountRepository, PeerRepository peerRepository) {
return new MercuryRosterStore(accountId, peerRepository, accountRepository);
}
}

View File

@ -0,0 +1,51 @@
package org.mercury_im.messenger.xmpp;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smackx.csi.ClientStateIndicationManager;
import org.mercury_im.messenger.ClientStateListener;
import java.util.logging.Level;
import java.util.logging.Logger;
public class CsiManager implements ClientStateListener {
private final MercuryConnectionManager connectionManager;
private static final Logger LOGGER = Logger.getLogger(CsiManager.class.getName());
public CsiManager(MercuryConnectionManager connectionManager) {
this.connectionManager = connectionManager;
}
@Override
public void onClientInForeground() {
LOGGER.log(Level.INFO, "CSI: active");
for (MercuryConnection connection : connectionManager.getConnections()) {
tryCsiActive(connection);
}
}
@Override
public void onClientInBackground() {
LOGGER.log(Level.INFO, "CSI: inactive");
for (MercuryConnection connection : connectionManager.getConnections()) {
tryCsiInactive(connection);
}
}
private void tryCsiActive(MercuryConnection connection) {
try {
ClientStateIndicationManager.active(connection.getConnection());
} catch (SmackException.NotConnectedException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Sending CSI state 'active' failed.", e);
}
}
private void tryCsiInactive(MercuryConnection connection) {
try {
ClientStateIndicationManager.inactive(connection.getConnection());
} catch (SmackException.NotConnectedException | InterruptedException e) {
LOGGER.log(Level.WARNING, "Sending CSI state 'inactive' failed.", e);
}
}
}

View File

@ -5,19 +5,18 @@ import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.mercury_im.messenger.data.repository.AccountRepository;
import org.jivesoftware.smack.sasl.SASLErrorException;
import org.mercury_im.messenger.entity.Account;
import org.mercury_im.messenger.util.Optional;
import org.mercury_im.messenger.xmpp.exception.InvalidCredentialsException;
import org.mercury_im.messenger.xmpp.exception.ServerUnreachableException;
import org.mercury_im.messenger.xmpp.state.ConnectionState;
import org.mercury_im.messenger.xmpp.state.ConnectivityState;
import java.io.IOException;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.reactivex.Observable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.subjects.BehaviorSubject;
import lombok.Getter;
@ -46,19 +45,43 @@ public class MercuryConnection {
return state;
}
public void connect() throws InterruptedException, XMPPException, SmackException, IOException {
public MercuryConnection connect() throws ServerUnreachableException {
if (getConnection().isConnected()) {
return;
return this;
}
((AbstractXMPPConnection) getConnection()).connect();
doConnect();
return this;
}
public void login() throws InterruptedException, IOException, SmackException, XMPPException {
private MercuryConnection doConnect() throws ServerUnreachableException {
AbstractXMPPConnection connection = (AbstractXMPPConnection) getConnection();
try {
connection.connect();
return this;
} catch (SmackException.EndpointConnectionException e) {
connection.disconnect();
throw new ServerUnreachableException("Cannot connect to server " + connection.getXMPPServiceDomain().asUnescapedString(), e);
} catch (IOException | InterruptedException | XMPPException | SmackException e) {
throw new AssertionError("Unexpected exception.", e);
}
}
public void login() throws ServerUnreachableException, InvalidCredentialsException {
if (getConnection().isAuthenticated()) {
return;
}
connect();
((AbstractXMPPConnection) getConnection()).login();
doLogin();
}
private void doLogin() throws InvalidCredentialsException, ServerUnreachableException {
try {
connect();
((AbstractXMPPConnection) getConnection()).login();
} catch (SASLErrorException e) {
throw new InvalidCredentialsException("Credentials of account " + accountId + " are invalid.", e);
} catch (InterruptedException | XMPPException | SmackException | IOException e) {
throw new AssertionError("Unexpected exception.", e);
}
}
private final ConnectionListener connectionListener = new ConnectionListener() {

View File

@ -101,11 +101,14 @@ public class MercuryConnectionManager {
return connections.get(id);
}
public MercuryConnection createConnection(Account account) {
return new MercuryConnection(connectionFactory.createConnection(account), account);
}
public void registerConnections(List<Account> accounts) {
for (Account account : accounts) {
if (!connections.containsKey(account.getId())) {
MercuryConnection connection = new MercuryConnection(
connectionFactory.createConnection(account), account);
MercuryConnection connection = createConnection(account);
registerConnection(connection);
}
}

View File

@ -0,0 +1,10 @@
package org.mercury_im.messenger.xmpp.exception;
public class InvalidCredentialsException extends Exception {
private static final long serialVersionUID = 1L;
public InvalidCredentialsException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -0,0 +1,10 @@
package org.mercury_im.messenger.xmpp.exception;
public class ServerUnreachableException extends Exception {
private static final long serialVersionUID = 1L;
public ServerUnreachableException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,17 +0,0 @@
package org.mercury_im.messenger.learning_tests.smack;
import org.junit.Test;
import org.mercury_im.messenger.usecase.AddAccountUseCase;
import java.util.UUID;
public class AddAccountUseCaseTest {
@Test
public void test() {
AddAccountUseCase useCase;
AddAccountUseCase.AddAccountTask task;
System.out.println(UUID.randomUUID());
}
}

@ -1 +1 @@
Subproject commit a71ce2ca36faab3bd026dc492b0b8ab8266440d9
Subproject commit e79710840be6d6b3301e078a23688eafaa06013c