Rework Roster storage

This commit is contained in:
Paul Schaub 2019-08-19 02:36:31 +02:00
parent 83aa428a66
commit e661ffdf65
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
37 changed files with 1036 additions and 704 deletions

View File

@ -7,6 +7,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Build;
import org.jivesoftware.smack.SmackConfiguration;
import org.mercury_im.messenger.core.ConnectionCenter;
import org.mercury_im.messenger.di.component.AppComponent;
import org.mercury_im.messenger.di.component.DaggerAppComponent;
@ -33,6 +34,7 @@ public class MercuryImApplication extends Application {
@Override
public void onCreate() {
SmackConfiguration.DEBUG = true;
super.onCreate();
INSTANCE = this;

View File

@ -6,17 +6,15 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import org.mercury_im.messenger.persistence.room.model.RoomContactAttributes;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.repository.IContactAttributesRepository;
import org.mercury_im.messenger.persistence.room.repository.IContactRepository;
import org.mercury_im.messenger.persistence.room.repository.IRosterRepository;
import javax.inject.Inject;
public class RosterItemViewModel extends AndroidViewModel {
@Inject
IContactRepository contactRepository;
IRosterRepository contactRepository;
private LiveData<RoomContactModel> contact;

View File

@ -81,7 +81,7 @@ public class RosterRecyclerViewAdapter
}
void bind(RoomContactModel contactModel) {
String nick = contactModel.getContact().getNickname();
String nick = contactModel.getNickname();
nicknameView.setText(nick != null ? nick : "");
EntityBareJid jid = contactModel.getEntity().getJid();
jidView.setText(jid.toString());

View File

@ -1,13 +1,15 @@
package org.mercury_im.messenger.ui.roster;
import android.util.Log;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.persistence.repository.ContactRepository;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.repository.IContactRepository;
import java.util.List;
@ -15,12 +17,13 @@ import javax.inject.Inject;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
public class RosterViewModel extends ViewModel {
@Inject
ContactRepository contactRepository;
RosterRepository rosterRepository;
private final MutableLiveData<List<RoomContactModel>> rosterEntryList = new MutableLiveData<>();
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
@ -28,12 +31,15 @@ public class RosterViewModel extends ViewModel {
public RosterViewModel() {
super();
MercuryImApplication.getApplication().getAppComponent().inject(this);
Log.d("RosterViewModel", "Start observing database");
// Subscribe to changes to the contacts table and update the LiveData object for the UI.
compositeDisposable.add(contactRepository.getAllContacts()
compositeDisposable.add(rosterRepository.getAllContacts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(o -> rosterEntryList.setValue((List<RoomContactModel>) o)));
.subscribe((Consumer<List<? extends ContactModel>>) o -> {
Log.d("RosterViewModel", "Room changed contacts: " + o.size());
rosterEntryList.setValue((List<RoomContactModel>) o);
}));
}
@Override

View File

@ -6,6 +6,7 @@ import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.caps.EntityCapsManager;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import java.util.Collections;
import java.util.HashMap;
@ -32,6 +33,7 @@ public class ConnectionCenter {
// Injected
private final AccountRepository accountRepository;
private final EntityCapsStore entityCapsStore;
private final RosterRepository rosterRepository;
// Connections
private final Map<Long, MercuryConnection> connectionMap =
@ -43,10 +45,11 @@ public class ConnectionCenter {
private final AtomicBoolean started = new AtomicBoolean(false);
@Inject
public ConnectionCenter(EntityCapsStore capsStore, AccountRepository accountRepository) {
public ConnectionCenter(EntityCapsStore capsStore, AccountRepository accountRepository, RosterRepository rosterRepository) {
LOGGER.log(Level.INFO, "ConnectionCenter initialized");
this.accountRepository = accountRepository;
this.entityCapsStore = capsStore;
this.rosterRepository = rosterRepository;
EntityCapsManager.setPersistentCache(capsStore);
startUp();
@ -66,35 +69,42 @@ public class ConnectionCenter {
}
// otherwise subscribe to accounts and create connections.
disposable.add(accountRepository.getAllAccounts()
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.computation())
.subscribe((Consumer<List<? extends AccountModel>>) accounts -> {
Set<Long> accountIds = new HashSet<>();
// Add missing connections to the map
for (AccountModel account : accounts) {
accountIds.add(account.getId());
if (connectionMap.get(account.getId()) != null) {
continue;
}
MercuryConnection connection = createConnection(account);
connectionMap.put(account.getId(), connection);
disposable.add(
accountRepository.getAllAccounts()
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.computation())
.subscribe((Consumer<List<? extends AccountModel>>) accounts -> {
LOGGER.log(Level.INFO, "Accounts changed.");
Set<Long> accountIds = new HashSet<>();
// Add missing connections to the map
for (AccountModel account : accounts) {
accountIds.add(account.getId());
if (connectionMap.get(account.getId()) != null) {
continue;
}
LOGGER.log(Level.INFO, "Add new connection " + account.getJid().toString() + " to ConnectionCenter list.");
MercuryConnection connection = createConnection(account);
connectionMap.put(account.getId(), connection);
RosterStore rosterStore = new RosterStore(rosterRepository);
rosterStore.setAccountId(account.getId());
rosterStore.subscribe();
connection.getRoster().setRosterStore(rosterStore);
if (account.getEnabled()) {
connection.connect();
}
}
if (account.getEnabled()) {
connection.connect();
}
}
// Remove unwanted connections from the map
for (long connectionId : connectionMap.keySet()) {
if (!accountIds.contains(connectionId)) {
AbstractXMPPConnection con =
(AbstractXMPPConnection) connectionMap.get(connectionId).getConnection();
con.disconnect();
connectionMap.remove(connectionId);
}
}
}));
// Remove unwanted connections from the map
for (long connectionId : connectionMap.keySet()) {
if (!accountIds.contains(connectionId)) {
AbstractXMPPConnection con =
(AbstractXMPPConnection) connectionMap.get(connectionId).getConnection();
con.disconnect();
connectionMap.remove(connectionId);
}
}
}));
}
public MercuryConnection getConnection(AccountModel account) {
@ -115,8 +125,8 @@ public class ConnectionCenter {
.setHost(accountModel.getJid().getDomain().toString())
.setXmppAddressAndPassword(accountModel.getJid(), accountModel.getPassword())
.setConnectTimeout(2 * 60 * 1000)
//.setEnabledSSLCiphers(MercuryConfiguration.enabledCiphers)
//.setEnabledSSLProtocols(MercuryConfiguration.enabledProtocols)
.setEnabledSSLCiphers(MercuryConfiguration.enabledCiphers)
.setEnabledSSLProtocols(MercuryConfiguration.enabledProtocols)
.build();
AbstractXMPPConnection tcpConnection = new XMPPTCPConnection(configuration);

View File

@ -3,7 +3,6 @@ package org.mercury_im.messenger.core;
import org.jivesoftware.smack.parsing.SmackParsingException;
import org.jivesoftware.smack.xml.SmackXmlParser;
import org.jivesoftware.smack.xml.XmlPullParserException;
import org.jivesoftware.smackx.caps.EntityCapsManager;
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.jivesoftware.smackx.disco.provider.DiscoverInfoProvider;
@ -15,8 +14,6 @@ import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;

View File

@ -3,25 +3,21 @@ package org.mercury_im.messenger.core;
public class MercuryConfiguration {
public static final String[] enabledCiphers = {
"ECDHE-RSA-AES256-GCM-SHA384",
"ECDHE-RSA-AES128-GCM-SHA256",
"ECDHE-RSA-AES256-SHA384",
"ECDHE-RSA-AES128-SHA256",
"ECDHE-RSA-AES256-SHA",
"ECDHE-RSA-AES128-SHA",
"DHE-RSA-AES256-GCM-SHA384",
"DHE-RSA-AES128-GCM-SHA256",
"DHE-RSA-AES256-SHA256",
"DHE-RSA-AES128-SHA256",
"DHE-RSA-AES256-SHA",
"DHE-RSA-CAMELLIA256-SHA",
"DHE-RSA-AES128-SHA",
"DHE-RSA-SEED-SHA",
"DHE-RSA-CAMELLIA128-SHA"
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
"TLS_DHE_RSA_WITH_AES_128_CBC_SHA"
};
public static final String[] enabledProtocols = {
"TLSv1.1",
"TLSv1.2",
"TLSv1.3"
};

View File

@ -49,6 +49,7 @@ public class MercuryConnection {
reconnectionManager.enableAutomaticReconnection();
this.roster = Roster.getInstanceFor(connection);
roster.setRosterLoadedAtLogin(true);
this.chatManager = ChatManager.getInstanceFor(connection);
this.carbonManager = CarbonManager.getInstanceFor(connection);
this.stanzaIdManager = StableUniqueStanzaIdManager.getInstanceFor(connection);
@ -57,7 +58,6 @@ public class MercuryConnection {
this.serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection);
VersionManager.getInstanceFor(connection).setVersion("Mercury", "0.0.1-stealth", "Android");
serviceDiscoveryManager.setIdentity(new DiscoverInfo.Identity("client", "Mercury", "phone"));
roster.setRosterLoadedAtLogin(true);
}
public void connect() {

View File

@ -2,37 +2,77 @@ package org.mercury_im.messenger.core;
import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jxmpp.jid.Jid;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.repository.ContactRepository;
import org.mercury_im.messenger.persistence.model.RosterInformationModel;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import io.reactivex.MaybeObserver;
import io.reactivex.Observer;
import io.reactivex.Scheduler;
import io.reactivex.disposables.Disposable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.RosterStore {
private final ContactRepository contactRepository;
private static final Logger LOGGER = Logger.getLogger(RosterStore.class.getName());
private final RosterRepository rosterRepository;
private long accountId;
@Inject
public RosterStore(ContactRepository contactRepository) {
this.contactRepository = contactRepository;
private CompositeDisposable disposable = null;
contactRepository.getAllContactsOfAccount(accountId)
private final Map<Jid, RosterPacket.Item> itemMap = new HashMap<>();
private String rosterVersion;
@Inject
public RosterStore(RosterRepository rosterRepository) {
this.rosterRepository = rosterRepository;
}
public void subscribe() {
LOGGER.log(Level.INFO, "Subscribing...");
if (disposable != null) {
return;
}
disposable = new CompositeDisposable();
disposable.add(rosterRepository.getAllContactsOfAccount(accountId)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
.subscribe((Consumer<List<? extends ContactModel>>) o -> {
itemMap.clear();
for (ContactModel c : o) {
rosterRepository.getEntityForContact(c.getId()).subscribeOn(Schedulers.io())
.subscribe((Consumer<EntityModel>) o1 -> itemMap.put(o1.getJid(), fromModel(c)));
LOGGER.log(Level.INFO, "Populate itemMap with " + o.size() + " items");
}
}));
disposable.add(rosterRepository.getRosterInformationForAccount(accountId)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe((Consumer<RosterInformationModel>) s -> {
LOGGER.log(Level.INFO, "Set rosterVer = " + s.getRosterVersion());
rosterVersion = s.getRosterVersion();
}));
}
public void unsubscribe() {
if (disposable == null) {
return;
}
disposable.dispose();
disposable = null;
}
public void setAccountId(long accountId) {
@ -41,70 +81,116 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
@Override
public List<RosterPacket.Item> getEntries() {
return new ArrayList<>(itemMap.values());
}
@Override
public RosterPacket.Item getEntry(Jid bareJid) {
return null;
return itemMap.get(bareJid);
}
@Override
public String getRosterVersion() {
return null;
return rosterVersion != null ? rosterVersion : "";
}
@Override
public boolean addEntry(RosterPacket.Item item, String version) {
return false;
LOGGER.log(Level.INFO, "Add entry " + item.toXML().toString());
// Update database
ContactModel contact = toModel(item);
rosterRepository.updateOrInsertContact(contact)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
rosterRepository.updateRosterVersionForAccount(accountId, version)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
return true;
}
@Override
public boolean resetEntries(Collection<RosterPacket.Item> items, String version) {
return false;
// Update database
for (RosterPacket.Item item : items) {
rosterRepository.updateOrInsertContact(toModel(item))
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
}
rosterRepository.updateRosterVersionForAccount(accountId, version)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
return true;
}
@Override
public boolean removeEntry(Jid bareJid, String version) {
return false;
rosterRepository.deleteContact(accountId, bareJid.asEntityBareJidOrThrow())
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
rosterRepository.updateRosterVersionForAccount(accountId, version)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
return true;
}
@Override
public void resetStore() {
rosterRepository.deleteAllContactsOfAccount(accountId)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
rosterRepository.updateRosterVersionForAccount(accountId, "")
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
}
public RosterPacket.Item fromModel(ContactModel contactModel) {
RosterPacket.Item item = new RosterPacket.Item(contactModel.getEntity().getJid(), contactModel.getContact().getRosterName());
item.setItemType(convert(contactModel.getContact().getDirection()));
item.setApproved(contactModel.getContact().isApproved());
item.setSubscriptionPending(contactModel.getContact().isSubscriptionPending());
RosterPacket.Item item = new RosterPacket.Item(
contactModel.getEntity().getJid(),
contactModel.getRosterName());
if (contactModel.getDirection() != null) {
item.setItemType(convert(contactModel.getDirection()));
}
item.setApproved(contactModel.isApproved());
item.setSubscriptionPending(contactModel.isSubscriptionPending());
return item;
}
public ContactModel toModel(RosterPacket.Item item) {
ContactModel contact = contactRepository.newContactModel();
ContactModel contact = rosterRepository.newContactModel();
contact.setAccountId(accountId);
ContactAttributes attributes = contactRepository.newAttributesModel();
ContactModel attributes = rosterRepository.newContactModel();
attributes.setRosterName(item.getName());
attributes.setDirection(convert(item.getItemType()));
if (item.getItemType() != null) {
attributes.setDirection(convert(item.getItemType()));
}
attributes.setApproved(item.isApproved());
attributes.setSubscriptionPending(item.isSubscriptionPending());
EntityModel entity = contactRepository.newEntityModel();
EntityModel entity = rosterRepository.newEntityModel();
entity.setAccountId(accountId);
entity.setJid(item.getJid().asEntityBareJidOrThrow());
contact.setContact(attributes);
contact.setEntity(entity);
return contact;
}
public ContactAttributes.DIRECTION convert(RosterPacket.ItemType type) {
return ContactAttributes.DIRECTION.valueOf(type.toString());
public ContactModel.DIRECTION convert(RosterPacket.ItemType type) {
return ContactModel.DIRECTION.valueOf(type.toString());
}
public RosterPacket.ItemType convert(ContactAttributes.DIRECTION direction) {
public RosterPacket.ItemType convert(ContactModel.DIRECTION direction) {
return RosterPacket.ItemType.fromString(direction.toString());
}
}

View File

@ -3,9 +3,9 @@ package org.mercury_im.messenger.core.di;
import org.mercury_im.messenger.core.ConnectionCenter;
import org.mercury_im.messenger.core.EntityCapsStore;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
@ -16,8 +16,8 @@ public class CenterModule {
@Singleton
@Provides
static ConnectionCenter provideConnectionCenter(EntityCapsStore capsStore, AccountRepository accountRepository) {
return new ConnectionCenter(capsStore, accountRepository);
static ConnectionCenter provideConnectionCenter(EntityCapsStore capsStore, AccountRepository accountRepository, RosterRepository rosterRepository) {
return new ConnectionCenter(capsStore, accountRepository, rosterRepository);
}
@Singleton

View File

@ -27,6 +27,11 @@ android {
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
dependencies {

View File

@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "606370403d8f862397c7c75b88be193e",
"identityHash": "cc1de3c4fd3350ea0e894d02f30312f3",
"entities": [
{
"tableName": "contacts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_contact_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `fk_entity_id` INTEGER NOT NULL, `rostername` TEXT, `nickname` TEXT, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_contact_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `fk_entity_id` INTEGER NOT NULL, `rostername` TEXT, `nickname` TEXT, `direction` TEXT, `sub_pending` INTEGER NOT NULL, `approved` INTEGER NOT NULL, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
"fields": [
{
"fieldPath": "id",
@ -37,6 +37,24 @@
"columnName": "nickname",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "direction",
"columnName": "direction",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subscriptionPending",
"columnName": "sub_pending",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "approved",
"columnName": "approved",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
@ -473,12 +491,47 @@
}
],
"foreignKeys": []
},
{
"tableName": "roster_information",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_account_id` INTEGER NOT NULL, `roster_version` TEXT, PRIMARY KEY(`pk_account_id`))",
"fields": [
{
"fieldPath": "accountId",
"columnName": "pk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rosterVersion",
"columnName": "roster_version",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_account_id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_roster_information_pk_account_id",
"unique": true,
"columnNames": [
"pk_account_id"
],
"createSql": "CREATE UNIQUE INDEX `index_roster_information_pk_account_id` ON `${TABLE_NAME}` (`pk_account_id`)"
}
],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"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, '606370403d8f862397c7c75b88be193e')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'cc1de3c4fd3350ea0e894d02f30312f3')"
]
}
}

View File

@ -1,7 +1,6 @@
package org.mercury_im.messenger.persistence.room;
import android.content.Context;
import android.util.Log;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -9,19 +8,15 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
import org.mercury_im.messenger.persistence.room.repository.IAccountRepository;
import org.mercury_im.messenger.persistence.room.repository.IContactAttributesRepository;
import org.mercury_im.messenger.persistence.room.repository.IRosterRepository;
import org.mercury_im.messenger.persistence.room.repository.IMessageRepository;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Predicate;
import io.reactivex.observers.DefaultObserver;
import io.reactivex.schedulers.Schedulers;
import static org.junit.Assert.assertEquals;
@ -41,7 +36,7 @@ public class ExampleInstrumentedTest {
AppDatabase appDatabase = AppDatabase.getDatabase(context);
IAccountRepository accountRepository = new IAccountRepository(appDatabase.accountDao());
IContactAttributesRepository rosterRepository = new IContactAttributesRepository(appDatabase.rosterEntryDao());
IRosterRepository rosterRepository = new IRosterRepository(appDatabase.rosterEntryDao());
IMessageRepository messageRepository = new IMessageRepository(appDatabase.messageDao());
Observable<List<RoomAccountModel>> accounts = accountRepository.getAllAccounts();

View File

@ -1,6 +1,7 @@
package org.mercury_im.messenger.persistence.room;
import android.content.Context;
import android.util.Log;
import androidx.room.Database;
import androidx.room.Room;
@ -9,26 +10,31 @@ import androidx.room.RoomDatabase;
import org.mercury_im.messenger.persistence.room.dao.AccountDao;
import org.mercury_im.messenger.persistence.room.dao.AvatarDao;
import org.mercury_im.messenger.persistence.room.dao.ChatDao;
import org.mercury_im.messenger.persistence.room.dao.ContactAttributesDao;
import org.mercury_im.messenger.persistence.room.dao.ContactDao;
import org.mercury_im.messenger.persistence.room.dao.EntityCapsDao;
import org.mercury_im.messenger.persistence.room.dao.EntityDao;
import org.mercury_im.messenger.persistence.room.dao.MessageDao;
import org.mercury_im.messenger.persistence.room.dao.RosterInformationDao;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
import org.mercury_im.messenger.persistence.room.model.RoomAvatarModel;
import org.mercury_im.messenger.persistence.room.model.RoomChatModel;
import org.mercury_im.messenger.persistence.room.model.RoomContactAttributes;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.model.RoomEntityCapsModel;
import org.mercury_im.messenger.persistence.room.model.RoomEntityModel;
import org.mercury_im.messenger.persistence.room.model.RoomMessageModel;
import org.mercury_im.messenger.persistence.room.model.RoomRosterInformationModel;
import java.util.logging.Level;
import java.util.logging.Logger;
@Database(version = 1,
entities = {
RoomContactAttributes.class,
RoomAccountModel.class,
RoomEntityModel.class,
RoomContactModel.class,
RoomRosterInformationModel.class,
RoomChatModel.class,
RoomMessageModel.class,
RoomEntityModel.class,
RoomAvatarModel.class,
RoomEntityCapsModel.class
})
@ -39,6 +45,7 @@ public abstract class AppDatabase extends RoomDatabase {
public static AppDatabase getDatabase(final Context context) {
if (INSTANCE == null) {
Logger.getLogger("DATABASE").log(Level.INFO, context.getApplicationContext().getDatabasePath(DB_NAME).getAbsolutePath());
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, DB_NAME)
.build();
@ -46,19 +53,59 @@ public abstract class AppDatabase extends RoomDatabase {
return INSTANCE;
}
public abstract ContactAttributesDao rosterEntryDao();
public abstract ChatDao chatDao();
public abstract MessageDao messageDao();
/**
* Return an instance of {@link AccountDao}.
*
* @return accountDao
*/
public abstract AccountDao accountDao();
/**
* Return an instance of {@link EntityDao}.
*
* @return entityDao
*/
public abstract EntityDao entityDao();
/**
* Return an instance of {@link ContactDao}.
*
* @return contactDao
*/
public abstract ContactDao contactDao();
/**
* Return an instance of {@link RosterInformationDao}.
*
* @return rosterInformationDao
*/
public abstract RosterInformationDao rosterInformationDao();
/**
* Return an instance of {@link ChatDao}.
*
* @return chatDao
*/
public abstract ChatDao chatDao();
/**
* Return an instance of {@link MessageDao}.
*
* @return messageDao
*/
public abstract MessageDao messageDao();
/**
* Return an instance of {@link AvatarDao}.
*
* @return avatarDao
*/
public abstract AvatarDao avatarDao();
/**
* Return an instance of {@link EntityCapsDao}.
*
* @return entityCapsDao
*/
public abstract EntityCapsDao entityCapsDao();
}

View File

@ -5,11 +5,11 @@ import android.app.Application;
import org.mercury_im.messenger.persistence.room.dao.AccountDao;
import org.mercury_im.messenger.persistence.room.dao.AvatarDao;
import org.mercury_im.messenger.persistence.room.dao.ChatDao;
import org.mercury_im.messenger.persistence.room.dao.ContactAttributesDao;
import org.mercury_im.messenger.persistence.room.dao.ContactDao;
import org.mercury_im.messenger.persistence.room.dao.EntityCapsDao;
import org.mercury_im.messenger.persistence.room.dao.EntityDao;
import org.mercury_im.messenger.persistence.room.dao.MessageDao;
import org.mercury_im.messenger.persistence.room.dao.RosterInformationDao;
import javax.inject.Inject;
import javax.inject.Singleton;
@ -44,8 +44,8 @@ public class RoomModule {
@Singleton
@Provides
ContactAttributesDao provideRosterEntryDao() {
return mAppDatabase.rosterEntryDao();
ContactDao provideContactDao() {
return mAppDatabase.contactDao();
}
@Singleton
@ -68,14 +68,14 @@ public class RoomModule {
@Singleton
@Provides
ContactDao provideContactAndEntityDao() {
return mAppDatabase.contactDao();
AvatarDao providerAvatarDao() {
return mAppDatabase.avatarDao();
}
@Singleton
@Provides
AvatarDao providerAvatarDao() {
return mAppDatabase.avatarDao();
RosterInformationDao provideRosterInformationDao() {
return mAppDatabase.rosterInformationDao();
}
@Singleton

View File

@ -3,26 +3,22 @@ package org.mercury_im.messenger.persistence.room;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.repository.AvatarRepository;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.persistence.repository.ContactAttributesRepository;
import org.mercury_im.messenger.persistence.repository.ContactRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;
import org.mercury_im.messenger.persistence.repository.EntityRepository;
import org.mercury_im.messenger.persistence.repository.MessageRepository;
import org.mercury_im.messenger.persistence.room.dao.AccountDao;
import org.mercury_im.messenger.persistence.room.dao.AvatarDao;
import org.mercury_im.messenger.persistence.room.dao.ChatDao;
import org.mercury_im.messenger.persistence.room.dao.ContactAttributesDao;
import org.mercury_im.messenger.persistence.room.dao.ContactDao;
import org.mercury_im.messenger.persistence.room.dao.EntityCapsDao;
import org.mercury_im.messenger.persistence.room.dao.EntityDao;
import org.mercury_im.messenger.persistence.room.dao.MessageDao;
import org.mercury_im.messenger.persistence.room.dao.RosterInformationDao;
import org.mercury_im.messenger.persistence.room.repository.IAccountRepository;
import org.mercury_im.messenger.persistence.room.repository.IAvatarRepository;
import org.mercury_im.messenger.persistence.room.repository.IChatRepository;
import org.mercury_im.messenger.persistence.room.repository.IContactAttributesRepository;
import org.mercury_im.messenger.persistence.room.repository.IContactRepository;
import org.mercury_im.messenger.persistence.room.repository.IRosterRepository;
import org.mercury_im.messenger.persistence.room.repository.IEntityCapsRepository;
import org.mercury_im.messenger.persistence.room.repository.IEntityRepository;
import org.mercury_im.messenger.persistence.room.repository.IMessageRepository;
import javax.inject.Singleton;
@ -39,18 +35,6 @@ public class RoomRepositoryModule {
return new IAccountRepository(dao);
}
@Singleton
@Provides
EntityRepository provideEntityRepository(EntityDao dao) {
return new IEntityRepository(dao);
}
@Singleton
@Provides
ContactAttributesRepository provideContactAttributesRepository(ContactAttributesDao dao) {
return new IContactAttributesRepository(dao);
}
@Singleton
@Provides
ChatRepository provideChatRepository(ChatDao dao) {
@ -65,8 +49,8 @@ public class RoomRepositoryModule {
@Singleton
@Provides
ContactRepository provideContactRepository(ContactDao dao) {
return new IContactRepository(dao);
RosterRepository provideContactRepository(EntityDao entityDao, ContactDao contactDao, RosterInformationDao rosterInformationDao) {
return new IRosterRepository(entityDao, contactDao, rosterInformationDao);
}
@Singleton

View File

@ -1,49 +0,0 @@
package org.mercury_im.messenger.persistence.room.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.room.model.RoomContactAttributes;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import java.util.List;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import static androidx.room.OnConflictStrategy.REPLACE;
@Dao
@TypeConverters(EntityBareJidConverter.class)
public interface ContactAttributesDao extends BaseDao<RoomContactAttributes> {
@Override
@Insert(onConflict = REPLACE)
Single<Long> insert(RoomContactAttributes entity);
@Query("SELECT * FROM contacts WHERE pk_contact_id = :id")
Maybe<RoomContactAttributes> getContact(long id);
@Query("SELECT * FROM contacts WHERE fk_entity_id = :entityId")
Maybe<RoomContactAttributes> getContactForEntityId(long entityId);
/**
* Return a {@link Observable} wrapping a {@link List} of all {@link RoomContactAttributes RosterEntries}
* which are currently found in the database.
*
* @return
*/
@Query("SELECT * FROM contacts")
Observable<List<RoomContactAttributes>> getAllContacts();
@Query("SELECT contacts.* FROM contacts JOIN entities " +
"WHERE contacts.fk_account_id = :accountId AND jid = :jid")
Maybe<RoomContactAttributes> getContactByJid(long accountId, EntityBareJid jid);
@Query("SELECT * FROM contacts WHERE fk_account_id = :accountId")
Observable<List<RoomContactAttributes>> getContactsForAccount(long accountId);
}

View File

@ -1,14 +1,16 @@
package org.mercury_im.messenger.persistence.room.dao;
import androidx.room.Dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.Query;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.model.RoomEntityModel;
import org.mercury_im.messenger.persistence.room.model.RoomRosterInformation;
import org.mercury_im.messenger.persistence.room.model.RoomRosterInformationModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import java.util.List;
@ -16,46 +18,49 @@ import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import static androidx.room.OnConflictStrategy.REPLACE;
import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.PREFIX;
@Dao
@TypeConverters(EntityBareJidConverter.class)
public interface ContactDao {
@Query("SELECT contacts.*, " +
"entities." + RoomEntityModel.KEY_ID + " AS " + PREFIX + RoomEntityModel.KEY_ID + ", " +
"entities." + RoomEntityModel.KEY_ACCOUNT_ID + " AS " + PREFIX + RoomEntityModel.KEY_ACCOUNT_ID + ", " +
"entities." + RoomEntityModel.KEY_JID + " AS " + PREFIX + RoomEntityModel.KEY_JID + ", " +
"entities." + RoomEntityModel.KEY_AVATAR + " AS " + PREFIX + RoomEntityModel.KEY_AVATAR + " " +
"FROM contacts INNER JOIN entities ON contacts.fk_entity_id = entities.pk_entity_id " +
"WHERE entities.fk_account_id = :accountId AND entities.jid = :bareJid")
Maybe<RoomContactModel> getContactAndEntity(long accountId, EntityBareJid bareJid);
@Query("SELECT contacts.*, " +
"entities." + RoomEntityModel.KEY_ID + " AS " + PREFIX + RoomEntityModel.KEY_ID + ", " +
"entities." + RoomEntityModel.KEY_ACCOUNT_ID + " AS " + PREFIX + RoomEntityModel.KEY_ACCOUNT_ID + ", " +
"entities." + RoomEntityModel.KEY_JID + " AS " + PREFIX + RoomEntityModel.KEY_JID + ", " +
"entities." + RoomEntityModel.KEY_AVATAR + " AS " + PREFIX + RoomEntityModel.KEY_AVATAR + " " +
"FROM contacts INNER JOIN entities ON contacts.fk_entity_id = entities.pk_entity_id " +
"WHERE entities.fk_account_id = :accountId")
Observable<List<RoomContactModel>> getAllContactAndEntities(long accountId);
@Query("SELECT contacts.*, " +
"entities." + RoomEntityModel.KEY_ID + " AS " + PREFIX + RoomEntityModel.KEY_ID + ", " +
"entities." + RoomEntityModel.KEY_ACCOUNT_ID + " AS " + PREFIX + RoomEntityModel.KEY_ACCOUNT_ID + ", " +
"entities." + RoomEntityModel.KEY_JID + " AS " + PREFIX + RoomEntityModel.KEY_JID + ", " +
"entities." + RoomEntityModel.KEY_AVATAR + " AS " + PREFIX + RoomEntityModel.KEY_AVATAR + " " +
"FROM contacts INNER JOIN entities ON contacts.fk_entity_id = entities.pk_entity_id")
Observable<List<RoomContactModel>> getAllContactAndEntities();
@Query("SELECT * FROM roster_information")
Observable<List<RoomRosterInformation>> getAllRosterInformation();
@Query("SELECT * FROM roster_information WHERE pk_account_id = :accountId")
Maybe<RoomRosterInformation> getRosterInformation(long accountId);
public interface ContactDao extends BaseDao<RoomContactModel> {
@Override
@Insert(onConflict = REPLACE)
Completable insertRosterInformation(RoomRosterInformation information);
Single<Long> insert(RoomContactModel entity);
@Query("SELECT * FROM contacts WHERE pk_contact_id = :id")
Maybe<RoomContactModel> getContact(long id);
@Query("SELECT * FROM contacts WHERE fk_entity_id = :entityId")
Maybe<RoomContactModel> getContactForEntityId(long entityId);
@Query("SELECT * FROM contacts")
Observable<List<RoomContactModel>> getAllContacts();
@Query("SELECT contacts.* FROM contacts JOIN entities " +
"WHERE contacts.fk_account_id = :accountId AND jid = :jid")
Maybe<RoomContactModel> getContactByJid(long accountId, EntityBareJid jid);
@Query("SELECT * FROM contacts WHERE fk_account_id = :accountId")
Observable<List<RoomContactModel>> getContactsForAccount(long accountId);
@Query("DELETE FROM contacts WHERE pk_contact_id = :id")
Completable deleteContact(long id);
@Query("DELETE FROM contacts WHERE fk_entity_id = :entityId")
Completable deleteContactForEntity(long entityId);
@Query("DELETE FROM contacts")
Completable deleteAll();
@Query("DELETE FROM contacts WHERE fk_account_id = :accountId")
Completable deleteAllForAccount(long accountId);
@Query("DELETE FROM contacts WHERE pk_contact_id IN(:ids)")
Completable deleteContacts(long[] ids);
@Query("SELECT entities.* FROM contacts INNER JOIN entities ON contacts.fk_entity_id = entities.pk_entity_id WHERE contacts.pk_contact_id = :contactId")
Single<RoomEntityModel> getEntityForContactId(long contactId);
}

View File

@ -0,0 +1,27 @@
package org.mercury_im.messenger.persistence.room.dao;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;
import org.mercury_im.messenger.persistence.room.model.RoomRosterInformationModel;
import java.util.List;
import io.reactivex.Observable;
import io.reactivex.Single;
import static androidx.room.OnConflictStrategy.REPLACE;
@Dao
public interface RosterInformationDao extends BaseDao<RoomRosterInformationModel> {
@Query("SELECT * FROM roster_information")
Observable<List<RoomRosterInformationModel>> getAllRosterInformation();
@Query("SELECT * FROM roster_information WHERE pk_account_id = :accountId")
Observable<RoomRosterInformationModel> getRosterInformation(long accountId);
@Insert(onConflict = REPLACE)
Single<Long> insertRosterInformation(RoomRosterInformationModel information);
}

View File

@ -1,155 +0,0 @@
package org.mercury_im.messenger.persistence.room.model;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverter;
import androidx.room.TypeConverters;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import org.mercury_im.messenger.persistence.room.type_converter.DirectionConverter;
import static androidx.room.ForeignKey.CASCADE;
import static androidx.room.ForeignKey.RESTRICT;
import static org.mercury_im.messenger.persistence.room.model.RoomContactAttributes.KEY_ACCOUNT_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomContactAttributes.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomContactAttributes.KEY_ENTITY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomContactAttributes.TABLE;
@Entity(tableName = TABLE,
indices = {
@Index(value = KEY_ID),
@Index(value = KEY_ACCOUNT_ID),
@Index(value = KEY_ENTITY_ID),
@Index(value = {KEY_ID, KEY_ENTITY_ID}, unique = true)
},
foreignKeys = {
@ForeignKey(entity = RoomAccountModel.class,
parentColumns = RoomAccountModel.KEY_ID,
childColumns = KEY_ACCOUNT_ID,
onDelete = CASCADE),
@ForeignKey(entity = RoomEntityModel.class,
parentColumns = RoomEntityModel.KEY_ID,
childColumns = KEY_ENTITY_ID,
onDelete = RESTRICT)})
public class RoomContactAttributes implements ContactAttributes {
public static final String TABLE = "contacts";
public static final String KEY_ID = "pk_contact_id";
public static final String KEY_ACCOUNT_ID = "fk_account_id";
public static final String KEY_ENTITY_ID = "fk_entity_id";
public static final String KEY_ROSTER_NAME = "rostername";
public static final String KEY_NICKNAME = "nickname";
public static final String KEY_DIRECTION = "direction";
public static final String KEY_SUB_PENDING = "sub_pending";
public static final String KEY_APPROVED = "approved";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = KEY_ID)
private long id;
@ColumnInfo(name = KEY_ACCOUNT_ID)
private long accountId;
@ColumnInfo(name = KEY_ENTITY_ID)
private long entityId;
@ColumnInfo(name = KEY_ROSTER_NAME)
private String rosterName;
@ColumnInfo(name = KEY_NICKNAME)
private String nickname;
@ColumnInfo(name = KEY_DIRECTION)
@TypeConverters(DirectionConverter.class)
private DIRECTION direction;
@ColumnInfo(name = KEY_SUB_PENDING)
private boolean subscriptionPending;
@ColumnInfo(name = KEY_APPROVED)
private boolean approved;
@Override
public long getId() {
return id;
}
@Override
public void setId(long id) {
this.id = id;
}
@Override
public long getAccountId() {
return accountId;
}
@Override
public void setAccountId(long accountId) {
this.accountId = accountId;
}
@Override
public long getEntityId() {
return entityId;
}
@Override
public void setEntityId(long id) {
this.entityId = id;
}
@Override
public String getRosterName() {
return rosterName;
}
@Override
public void setRosterName(String rosterName) {
this.rosterName = rosterName;
}
@Override
public String getNickname() {
return nickname;
}
@Override
public void setNickname(String nickname) {
this.nickname = nickname;
}
@Override
public DIRECTION getDirection() {
return direction;
}
@Override
public void setDirection(DIRECTION direction) {
this.direction = direction;
}
@Override
public boolean isSubscriptionPending() {
return subscriptionPending;
}
@Override
public void setSubscriptionPending(boolean pending) {
this.subscriptionPending = pending;
}
@Override
public boolean isApproved() {
return approved;
}
@Override
public void setApproved(boolean approved) {
this.approved = approved;
}
}

View File

@ -1,34 +1,173 @@
package org.mercury_im.messenger.persistence.room.model;
import androidx.room.Embedded;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.room.type_converter.DirectionConverter;
public class RoomContactModel implements ContactModel<RoomContactAttributes, RoomEntityModel> {
import static androidx.room.ForeignKey.CASCADE;
import static androidx.room.ForeignKey.RESTRICT;
import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.KEY_ACCOUNT_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.KEY_ENTITY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomContactModel.TABLE;
public static final String PREFIX = "e_";
@Entity(tableName = TABLE,
indices = {
@Index(value = KEY_ID),
@Index(value = KEY_ACCOUNT_ID),
@Index(value = KEY_ENTITY_ID),
@Index(value = {KEY_ID, KEY_ENTITY_ID}, unique = true)
},
foreignKeys = {
@ForeignKey(entity = RoomAccountModel.class,
parentColumns = RoomAccountModel.KEY_ID,
childColumns = KEY_ACCOUNT_ID,
onDelete = CASCADE),
@ForeignKey(entity = RoomEntityModel.class,
parentColumns = RoomEntityModel.KEY_ID,
childColumns = KEY_ENTITY_ID,
onDelete = RESTRICT)})
public class RoomContactModel implements ContactModel {
@Embedded(prefix = PREFIX)
private RoomEntityModel entity;
public static final String TABLE = "contacts";
public static final String KEY_ID = "pk_contact_id";
public static final String KEY_ACCOUNT_ID = "fk_account_id";
public static final String KEY_ENTITY_ID = "fk_entity_id";
public static final String KEY_ROSTER_NAME = "rostername";
public static final String KEY_NICKNAME = "nickname";
public static final String KEY_DIRECTION = "direction";
public static final String KEY_SUB_PENDING = "sub_pending";
public static final String KEY_APPROVED = "approved";
@Embedded()
private RoomContactAttributes contact;
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = KEY_ID)
private long id;
@ColumnInfo(name = KEY_ACCOUNT_ID)
private long accountId;
@ColumnInfo(name = KEY_ENTITY_ID)
private long entityId;
@ColumnInfo(name = KEY_ROSTER_NAME)
private String rosterName;
@ColumnInfo(name = KEY_NICKNAME)
private String nickname;
@ColumnInfo(name = KEY_DIRECTION)
@TypeConverters(DirectionConverter.class)
private DIRECTION direction;
@ColumnInfo(name = KEY_SUB_PENDING)
private boolean subscriptionPending;
@ColumnInfo(name = KEY_APPROVED)
private boolean approved;
/**
* This field is ignored by room and must instead be populated manually by calling
* {@link #setEntity(EntityModel)}.
*/
@Ignore
private EntityModel entityModel;
@Override
public RoomContactAttributes getContact() {
return contact;
}
public void setContact(RoomContactAttributes contact) {
this.contact = contact;
public long getId() {
return id;
}
@Override
public RoomEntityModel getEntity() {
return entity;
public void setId(long id) {
this.id = id;
}
public void setEntity(RoomEntityModel entity) {
this.entity = entity;
@Override
public long getAccountId() {
return accountId;
}
@Override
public void setAccountId(long accountId) {
this.accountId = accountId;
}
@Override
public long getEntityId() {
return entityId;
}
@Override
public void setEntityId(long id) {
this.entityId = id;
}
@Override
public String getRosterName() {
return rosterName;
}
@Override
public void setRosterName(String rosterName) {
this.rosterName = rosterName;
}
@Override
public String getNickname() {
return nickname;
}
@Override
public void setNickname(String nickname) {
this.nickname = nickname;
}
@Override
public DIRECTION getDirection() {
return direction;
}
@Override
public void setDirection(DIRECTION direction) {
this.direction = direction;
}
@Override
public boolean isSubscriptionPending() {
return subscriptionPending;
}
@Override
public void setSubscriptionPending(boolean pending) {
this.subscriptionPending = pending;
}
@Override
public boolean isApproved() {
return approved;
}
@Override
public void setApproved(boolean approved) {
this.approved = approved;
}
@Override
public EntityModel getEntity() {
return entityModel;
}
@Override
public void setEntity(EntityModel entity) {
this.entityModel = entity;
}
}

View File

@ -7,13 +7,13 @@ import androidx.room.PrimaryKey;
import org.mercury_im.messenger.persistence.model.RosterInformationModel;
import static org.mercury_im.messenger.persistence.room.model.RoomRosterInformation.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomRosterInformation.TABLE;
import static org.mercury_im.messenger.persistence.room.model.RoomRosterInformationModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomRosterInformationModel.TABLE;
@Entity(tableName = TABLE, indices = {
@Index(value = KEY_ID, unique = true)
})
public class RoomRosterInformation implements RosterInformationModel {
public class RoomRosterInformationModel implements RosterInformationModel {
public static final String TABLE = "roster_information";
public static final String KEY_ID = "pk_account_id";

View File

@ -1,6 +1,5 @@
package org.mercury_im.messenger.persistence.room.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.room.dao.AccountDao;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;

View File

@ -2,7 +2,6 @@ package org.mercury_im.messenger.persistence.room.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
@ -51,14 +50,9 @@ public class IChatRepository implements ChatRepository<RoomChatModel> {
return chatDao.getChatWithJid(account.getId(), jid);
}
@Override
public Maybe<RoomChatModel> getChatWith(ContactAttributes contact) {
return chatDao.getChatWithContact(contact.getId());
}
@Override
public Maybe<RoomChatModel> getChatWith(ContactModel contact) {
return getChatWith(contact.getContact());
return chatDao.getChatWithContact(contact.getId());
}
@Override

View File

@ -1,47 +0,0 @@
package org.mercury_im.messenger.persistence.room.repository;
import org.mercury_im.messenger.persistence.repository.ContactAttributesRepository;
import org.mercury_im.messenger.persistence.room.dao.ContactAttributesDao;
import org.mercury_im.messenger.persistence.room.model.RoomContactAttributes;
import java.util.List;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
public class IContactAttributesRepository implements ContactAttributesRepository<RoomContactAttributes> {
private final ContactAttributesDao contactAttributesDao;
public IContactAttributesRepository(ContactAttributesDao dao) {
this.contactAttributesDao = dao;
}
@Override
public RoomContactAttributes newContactAttributesModel() {
return new RoomContactAttributes();
}
@Override
public Observable<List<RoomContactAttributes>> getAllContactAttributes() {
return contactAttributesDao.getAllContacts();
}
@Override
public Single<Long> updateOrInsertContactAttributes(RoomContactAttributes attributes) {
return contactAttributesDao.insert(attributes);
}
@Override
public Maybe<RoomContactAttributes> getContactAttributes(long id) {
return contactAttributesDao.getContact(id);
}
@Override
public Maybe<RoomContactAttributes> getContactAttributesForEntity(long entityId) {
return contactAttributesDao.getContactForEntityId(entityId);
}
}

View File

@ -1,80 +0,0 @@
package org.mercury_im.messenger.persistence.room.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.model.RosterInformationModel;
import org.mercury_im.messenger.persistence.repository.ContactRepository;
import org.mercury_im.messenger.persistence.room.dao.ContactDao;
import org.mercury_im.messenger.persistence.room.model.RoomContactAttributes;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.model.RoomEntityModel;
import org.mercury_im.messenger.persistence.room.model.RoomRosterInformation;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
public class IContactRepository implements ContactRepository<RoomContactModel, RoomRosterInformation> {
private final ContactDao dao;
@Inject
public IContactRepository(ContactDao dao) {
this.dao = dao;
}
@Override
public RoomContactModel newContactModel() {
return new RoomContactModel();
}
@Override
public ContactAttributes newAttributesModel() {
return new RoomContactAttributes();
}
@Override
public EntityModel newEntityModel() {
return new RoomEntityModel();
}
@Override
public Maybe<RoomContactModel> getContact(long accountId, EntityBareJid jid) {
return dao.getContactAndEntity(accountId, jid);
}
@Override
public Maybe<RoomContactModel> getContactForEntity(EntityModel entityModel) {
return getContact(entityModel.getAccountId(), entityModel.getJid());
}
@Override
public Observable<List<RoomContactModel>> getAllContactsOfAccount(long accountId) {
return dao.getAllContactAndEntities(accountId);
}
@Override
public Observable<List<RoomContactModel>> getAllContacts() {
return dao.getAllContactAndEntities();
}
@Override
public Observable<List<RoomRosterInformation>> getAllRosterInformation() {
return dao.getAllRosterInformation();
}
@Override
public Completable insertRosterInformation(RoomRosterInformation rosterInfo) {
return dao.insertRosterInformation(rosterInfo);
}
@Override
public Maybe<RoomRosterInformation> getRosterInformation(long accountId) {
return dao.getRosterInformation(accountId);
}
}

View File

@ -1,36 +0,0 @@
package org.mercury_im.messenger.persistence.room.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import org.mercury_im.messenger.persistence.repository.EntityRepository;
import org.mercury_im.messenger.persistence.room.dao.EntityDao;
import org.mercury_im.messenger.persistence.room.model.RoomEntityModel;
import javax.inject.Inject;
import io.reactivex.Maybe;
public class IEntityRepository implements EntityRepository<RoomEntityModel> {
private final EntityDao dao;
@Inject
public IEntityRepository(EntityDao dao) {
this.dao = dao;
}
@Override
public RoomEntityModel newEntityModel() {
return new RoomEntityModel();
}
@Override
public <C extends ContactAttributes> Maybe<RoomEntityModel> getEntity(C contact) {
return dao.getEntity(contact.getId());
}
@Override
public Maybe<RoomEntityModel> getEntity(long accountId, EntityBareJid jid) {
return dao.getEntityFor(accountId, jid);
}
}

View File

@ -7,6 +7,8 @@ import org.mercury_im.messenger.persistence.room.model.RoomMessageModel;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
@ -15,6 +17,7 @@ public class IMessageRepository implements MessageRepository<RoomMessageModel> {
private final MessageDao messageDao;
@Inject
public IMessageRepository(MessageDao messageDao) {
this.messageDao = messageDao;
}

View File

@ -0,0 +1,190 @@
package org.mercury_im.messenger.persistence.room.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.room.dao.ContactDao;
import org.mercury_im.messenger.persistence.room.dao.EntityDao;
import org.mercury_im.messenger.persistence.room.dao.RosterInformationDao;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.model.RoomEntityModel;
import org.mercury_im.messenger.persistence.room.model.RoomRosterInformationModel;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.CompletableSource;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.functions.Function;
public class IRosterRepository extends RosterRepository<RoomEntityModel, RoomContactModel, RoomRosterInformationModel> {
private final EntityDao entityDao;
private final ContactDao contactDao;
private final RosterInformationDao rosterInformationDao;
@Inject
public IRosterRepository(EntityDao entityDao, ContactDao contactDao, RosterInformationDao rosterInformationDao) {
this.contactDao = contactDao;
this.entityDao = entityDao;
this.rosterInformationDao = rosterInformationDao;
}
/*
RoomContactModel
*/
@Override
public RoomContactModel newContactModel() {
return new RoomContactModel();
}
@Override
public Observable<List<RoomContactModel>> getAllContacts() {
return contactDao.getAllContacts()
.flatMap(list -> {
for (RoomContactModel contact : list) {
RoomEntityModel entity = getEntityForContact(contact).blockingGet();
contact.setEntity(entity);
}
return Observable.just(list);
});
}
@Override
public Observable<List<RoomContactModel>> getAllContactsOfAccount(long accountId) {
return contactDao.getContactsForAccount(accountId)
.flatMap(list -> {
for (RoomContactModel contact : list) {
RoomEntityModel entity = getEntityForContact(contact).blockingGet();
contact.setEntity(entity);
}
return Observable.just(list);
});
}
@Override
public Single<Long> updateOrInsertContact(RoomContactModel contact) {
return entityDao.insert((RoomEntityModel) contact.getEntity())
.flatMap(entityId -> {
contact.getEntity().setId(entityId);
contact.setEntityId(entityId);
return contactDao.insert(contact);
});
}
@Override
public Single<RoomEntityModel> getEntityForContact(long contactId) {
Single<RoomEntityModel> s = contactDao.getEntityForContactId(contactId);
return s;
}
@Override
public Maybe<RoomContactModel> getContact(long id) {
return contactDao.getContact(id)
// Set the entity
.zipWith(getEntityForContact(id).toMaybe(),
(contact, entity) -> {
contact.setEntity(entity);
contact.setEntityId(entity.getId());
return contact;
});
}
@Override
public Maybe<RoomContactModel> getContactForEntity(long entityId) {
return contactDao.getContactForEntityId(entityId)
// Set the entity
.zipWith(getEntity(entityId),
(contact, entity) -> {
contact.setEntity(entity);
contact.setEntityId(entityId);
return contact;
});
}
@Override
public Completable deleteContact(long id) {
return contactDao.deleteContact(id);
}
@Override
public Completable deleteContact(RoomContactModel contact) {
return contactDao.delete(contact);
}
@Override
public Completable deleteContact(long accountId, EntityBareJid jid) {
// Since Room does not support "DELETE x FROM X x INNER JOIN Y...", we have to get the
// entity for the jid first and then delete by using its entityId
final Maybe<RoomEntityModel> entity = getEntity(accountId, jid.asEntityBareJidOrThrow());
return entity.flatMapCompletable(new Function<RoomEntityModel, CompletableSource>() {
@Override
public CompletableSource apply(RoomEntityModel entityModel) {
return contactDao.deleteContactForEntity(entityModel.getId());
}
});
}
@Override
public Completable deleteAllContacts() {
return contactDao.deleteAll();
}
@Override
public Completable deleteAllContactsOfAccount(long accountId) {
return contactDao.deleteAllForAccount(accountId);
}
@Override
public Completable deleteContacts(long[] ids) {
return contactDao.deleteContacts(ids);
}
/*
RoomRosterInformationModel
*/
@Override
public RoomRosterInformationModel newRosterInformationModel() {
return new RoomRosterInformationModel();
}
@Override
public Observable<RoomRosterInformationModel> getRosterInformationForAccount(long accountId) {
return rosterInformationDao.getRosterInformation(accountId);
}
@Override
public Single<Long> updateRosterInformation(RoomRosterInformationModel rosterInformation) {
return rosterInformationDao.insertRosterInformation(rosterInformation);
}
/*
RoomEntityModel
*/
@Override
public RoomEntityModel newEntityModel() {
return new RoomEntityModel();
}
@Override
public Maybe<RoomEntityModel> getEntity(long id) {
return entityDao.getEntity(id);
}
@Override
public Maybe<RoomEntityModel> getEntity(long accountId, EntityBareJid jid) {
return entityDao.getEntityFor(accountId, jid);
}
@Override
public Single<Long> insertOrReplaceEntity(RoomEntityModel entity) {
return entityDao.insert(entity);
}
}

View File

@ -2,17 +2,17 @@ package org.mercury_im.messenger.persistence.room.type_converter;
import androidx.room.TypeConverter;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import org.mercury_im.messenger.persistence.model.ContactModel;
public class DirectionConverter {
@TypeConverter
public static String toString(ContactAttributes.DIRECTION direction) {
return direction.toString();
public static String toString(ContactModel.DIRECTION direction) {
return direction != null ? direction.toString() : null;
}
@TypeConverter
public static ContactAttributes.DIRECTION fromString(String string) {
return ContactAttributes.DIRECTION.valueOf(string);
public static ContactModel.DIRECTION fromString(String string) {
return string != null ? ContactModel.DIRECTION.valueOf(string) : null;
}
}

View File

@ -1,44 +0,0 @@
package org.mercury_im.messenger.persistence.model;
public interface ContactAttributes {
long getId();
void setId(long id);
long getAccountId();
void setAccountId(long id);
long getEntityId();
void setEntityId(long id);
String getRosterName();
void setRosterName(String rosterName);
String getNickname();
void setNickname(String nickname);
DIRECTION getDirection();
void setDirection(DIRECTION direction);
boolean isSubscriptionPending();
void setSubscriptionPending(boolean pending);
boolean isApproved();
void setApproved(boolean approved);
enum DIRECTION {
none,
to,
from,
both,
remove
}
}

View File

@ -1,12 +1,48 @@
package org.mercury_im.messenger.persistence.model;
public interface ContactModel<C extends ContactAttributes, E extends EntityModel> {
public interface ContactModel<E extends EntityModel> {
C getContact();
long getId();
void setContact(C contact);
void setId(long id);
long getAccountId();
void setAccountId(long id);
long getEntityId();
void setEntityId(long id);
String getRosterName();
void setRosterName(String rosterName);
String getNickname();
void setNickname(String nickname);
DIRECTION getDirection();
void setDirection(DIRECTION direction);
boolean isSubscriptionPending();
void setSubscriptionPending(boolean pending);
boolean isApproved();
void setApproved(boolean approved);
E getEntity();
void setEntity(E entity);
enum DIRECTION {
none,
to,
from,
both,
remove
}
}

View File

@ -3,7 +3,6 @@ package org.mercury_im.messenger.persistence.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.model.ChatModel;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.EntityModel;
@ -25,8 +24,6 @@ public interface ChatRepository<E extends ChatModel> {
Maybe<E> getChatWith(EntityModel identity);
Maybe<E> getChatWith(ContactAttributes contact);
Maybe<E> getChatWith(ContactModel contact);
Completable closeChat(E chat);

View File

@ -1,22 +0,0 @@
package org.mercury_im.messenger.persistence.repository;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import java.util.List;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
public interface ContactAttributesRepository<E extends ContactAttributes> {
E newContactAttributesModel();
Observable<List<E>> getAllContactAttributes();
Single<Long> updateOrInsertContactAttributes(E attributes);
Maybe<E> getContactAttributes(long id);
Maybe<E> getContactAttributesForEntity(long entityId);
}

View File

@ -1,36 +0,0 @@
package org.mercury_im.messenger.persistence.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.model.RosterInformationModel;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
public interface ContactRepository<CE extends ContactModel<? extends ContactAttributes, ? extends EntityModel>, I extends RosterInformationModel> {
CE newContactModel();
ContactAttributes newAttributesModel();
EntityModel newEntityModel();
Maybe<CE> getContact(long accountId, EntityBareJid jid);
Maybe<CE> getContactForEntity(EntityModel entityModel);
Observable<List<CE>> getAllContactsOfAccount(long accountId);
Observable<List<CE>> getAllContacts();
Observable<List<I>> getAllRosterInformation();
Maybe<I> getRosterInformation(long accountId);
Completable insertRosterInformation(I rosterInfo);
}

View File

@ -1,17 +0,0 @@
package org.mercury_im.messenger.persistence.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.ContactAttributes;
import org.mercury_im.messenger.persistence.model.EntityModel;
import io.reactivex.Maybe;
public interface EntityRepository<E extends EntityModel> {
E newEntityModel();
<C extends ContactAttributes> Maybe<E> getEntity(C contact);
Maybe<E> getEntity(long accountId, EntityBareJid jid);
}

View File

@ -0,0 +1,249 @@
package org.mercury_im.messenger.persistence.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.model.RosterInformationModel;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
public abstract class RosterRepository<E extends EntityModel, C extends ContactModel, V extends RosterInformationModel> {
/*
ContactModel
*/
/**
* Create a new empty concrete {@link ContactModel}.
* This method is used as a factory method, as the core module will not know about any concrete
* implementations of {@link ContactModel}.
*
* @return new empty {@link ContactModel}
*/
public abstract C newContactModel();
/**
* Return an observable list of {@link ContactModel ContactModels}.
* Note: This method will return all contacts of all accounts.
*
* @return ALL {@link ContactModel ContactModels} currently in the database.
*/
public abstract Observable<List<C>> getAllContacts();
/**
* Return an observable list of all {@link ContactModel ContactModels} that belong to
* the account with the given accountId.
*
* @param accountId ID of the account
* @return all {@link ContactModel ContactModels} of the account
*/
public abstract Observable<List<C>> getAllContactsOfAccount(long accountId);
/**
* Return an observable list of all {@link ContactModel ContactModels} that belong to the
* given {@link AccountModel}.
*
* @param account account
* @return all {@link ContactModel ContactModels} of the account
*/
public Observable<List<C>> getAllContactsOfAccount(AccountModel account) {
return getAllContactsOfAccount(account.getId());
}
/**
* Update or insert an {@link ContactModel}.
* The method returns a {@link Single} which wraps the ID assigned to the {@link ContactModel}.
*
* @param contact contact
* @return single wrapping the contacts ID
*/
public abstract Single<Long> updateOrInsertContact(C contact);
/**
* Return the {@link ContactModel} corresponding to the given ID wrapped in a {@link Maybe}.
*
* @param id ID of the contact
* @return {@link Maybe} wrapping the {@link ContactModel}
*/
public abstract Maybe<C> getContact(long id);
/**
* Return the {@link ContactModel} which belongs to the given entityId, wrapped in a {@link Maybe}.
*
* @param entityId ID of the entity
* @return {@link Maybe} wrapping the {@link ContactModel}.
*/
public abstract Maybe<C> getContactForEntity(long entityId);
public Maybe<C> getContactForEntity(EntityModel entityModel) {
return getContactForEntity(entityModel.getId());
}
/**
* Delete a {@link ContactModel} from the database.
* This method returns a {@link Completable} which will invoke {@link Completable#complete()}
* once the {@link ContactModel} was successfully deleted, or will invoke
* {@link Completable#error(Throwable)} if an error occurs.
*
* @param id ID of the {@link ContactModel}
* @return {@link Completable}
*/
public abstract Completable deleteContact(long id);
/**
* Delete a {@link ContactModel} from the database.
* This method returns a {@link Completable} which will invoke {@link Completable#complete()}
* once the {@link ContactModel} was successfully deleted, or will invoke
* {@link Completable#error(Throwable)} if an error occurs.
*
* @param contact the {@link ContactModel} to be deleted
* @return {@link Completable}
*/
public Completable deleteContact(C contact) {
return deleteContact(contact.getId());
}
public abstract Completable deleteContact(long accountId, EntityBareJid jid);
public abstract Completable deleteAllContacts();
public abstract Completable deleteAllContactsOfAccount(long accountId);
public Completable deleteAllContactsOfAccount(AccountModel account) {
return deleteAllContactsOfAccount(account.getId());
}
public abstract Completable deleteContacts(long[] ids);
public Completable deleteContacts(C[] contacts) {
long[] ids = new long[contacts.length];
for (int i = 0; i < contacts.length; i++) {
ids[i] = contacts[i].getId();
}
return deleteContacts(ids);
}
/*
RosterInformationModel
*/
/**
* Create an empty, concrete implementation of {@link RosterInformationModel}.
* This method is used as a factory method, as the core module may not know about the concrete
* implementation of {@link RosterInformationModel} in use.
*
* @return concrete, empty {@link RosterInformationModel}
*/
public abstract V newRosterInformationModel();
/**
* Return an {@link Observable} wrapping the {@link RosterInformationModel} for the given
* account id.
*
* @param accountId ID of the account
* @return {@link Observable} wrapping the {@link RosterInformationModel}.
*/
public abstract Observable<V> getRosterInformationForAccount(long accountId);
/**
* Return an {@link Observable} wrapping the {@link RosterInformationModel} for the given
* account.
*
* @param account account
* @return {@link Observable} wrapping the {@link RosterInformationModel}.
*/
public Observable<V> getRosterInformationForAccount(AccountModel account) {
return getRosterInformationForAccount(account.getId());
}
public abstract Single<Long> updateRosterInformation(V rosterInformation);
public Single<Long> updateRosterVersionForAccount(long accountID, String rosterVersion) {
V info = newRosterInformationModel();
info.setAccountId(accountID);
info.setRosterVersion(rosterVersion);
return updateRosterInformation(info);
}
public Single<Long> updateRosterVersionForAccount(AccountModel accountModel, String rosterVersion) {
return updateRosterVersionForAccount(accountModel.getId(), rosterVersion);
}
/*
EntityModel
*/
/**
* Create an empty instance of a concrete implementation of the {@link EntityModel} class.
* This method is used as a factory method, as the core module may not know about the concrete
* implementation of {@link EntityModel} in use.
*
* @return new empty concrete implementation object.
*/
public abstract E newEntityModel();
/**
* Return the {@link EntityModel} with the given ID wrapped in a {@link Maybe}.
*
* @param id ID of the {@link EntityModel}
* @return {@link Maybe} wrapping the {@link EntityModel}
*/
public abstract Maybe<E> getEntity(long id);
/**
* Return the {@link EntityModel} which belongs to the given {@link ContactModel}.
* This method returns a {@link Single} instead of a {@link Maybe}, as per definition a
* {@link ContactModel} MUST have a corresponding {@link EntityModel} in the database.
*
* @param contact {@link ContactModel} of which we want to have the {@link EntityModel}
* @return {@link Single} wrapping the {@link EntityModel}
*/
public Single<E> getEntity(C contact) {
return getEntity(contact.getEntityId()).toSingle();
}
/**
* Return the {@link EntityModel} which belongs to the given accountID and jid, wrapped in a
* {@link Maybe}.
*
* @param accountId ID of the {@link AccountModel} to which the {@link EntityModel} points with
* a foreign key
* @param jid {@link EntityBareJid} of the {@link EntityModel}
* @return
*/
public abstract Maybe<E> getEntity(long accountId, EntityBareJid jid);
/**
* Return the {@link EntityModel} which has foreign keys pointing to the {@link AccountModel}
* and {@link EntityBareJid}, wrapped in a {@link Maybe}.
*
* @param account {@link AccountModel}
* @param jid {@link EntityBareJid}
* @return
*/
public Maybe<E> getEntity(AccountModel account, EntityBareJid jid) {
return getEntity(account.getId(), jid);
}
public abstract Single<E> getEntityForContact(long contactId);
public Single<E> getEntityForContact(C contact) {
return getEntityForContact(contact.getId());
}
/**
* Insert or replace a {@link EntityModel} in the database.
*
* @param entity {@link EntityModel} which we want to insert or replace in the database
* @return {@link Single} which wraps the ID assigned to the entity
*/
public abstract Single<Long> insertOrReplaceEntity(E entity);
}