diff --git a/app/src/main/java/org/mercury_im/messenger/MercuryImApplication.java b/app/src/main/java/org/mercury_im/messenger/MercuryImApplication.java index c707188..57c3946 100644 --- a/app/src/main/java/org/mercury_im/messenger/MercuryImApplication.java +++ b/app/src/main/java/org/mercury_im/messenger/MercuryImApplication.java @@ -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; diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterItemViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterItemViewModel.java index 4a5ca80..b6276ad 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterItemViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterItemViewModel.java @@ -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 contact; diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterRecyclerViewAdapter.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterRecyclerViewAdapter.java index 466ea04..9d2a620 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterRecyclerViewAdapter.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterRecyclerViewAdapter.java @@ -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()); diff --git a/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterViewModel.java b/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterViewModel.java index 71430d1..c5608d3 100644 --- a/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterViewModel.java +++ b/app/src/main/java/org/mercury_im/messenger/ui/roster/RosterViewModel.java @@ -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> 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) o))); + .subscribe((Consumer>) o -> { + Log.d("RosterViewModel", "Room changed contacts: " + o.size()); + rosterEntryList.setValue((List) o); + })); } @Override diff --git a/core/src/main/java/org/mercury_im/messenger/core/ConnectionCenter.java b/core/src/main/java/org/mercury_im/messenger/core/ConnectionCenter.java index 758326e..4362963 100644 --- a/core/src/main/java/org/mercury_im/messenger/core/ConnectionCenter.java +++ b/core/src/main/java/org/mercury_im/messenger/core/ConnectionCenter.java @@ -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 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>) accounts -> { - Set 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>) accounts -> { + LOGGER.log(Level.INFO, "Accounts changed."); + Set 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); diff --git a/core/src/main/java/org/mercury_im/messenger/core/EntityCapsStore.java b/core/src/main/java/org/mercury_im/messenger/core/EntityCapsStore.java index a7fe306..ba7d60c 100644 --- a/core/src/main/java/org/mercury_im/messenger/core/EntityCapsStore.java +++ b/core/src/main/java/org/mercury_im/messenger/core/EntityCapsStore.java @@ -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; diff --git a/core/src/main/java/org/mercury_im/messenger/core/MercuryConfiguration.java b/core/src/main/java/org/mercury_im/messenger/core/MercuryConfiguration.java index c0f1f6e..f4a27f7 100644 --- a/core/src/main/java/org/mercury_im/messenger/core/MercuryConfiguration.java +++ b/core/src/main/java/org/mercury_im/messenger/core/MercuryConfiguration.java @@ -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" }; diff --git a/core/src/main/java/org/mercury_im/messenger/core/MercuryConnection.java b/core/src/main/java/org/mercury_im/messenger/core/MercuryConnection.java index c1defd8..8c47c91 100644 --- a/core/src/main/java/org/mercury_im/messenger/core/MercuryConnection.java +++ b/core/src/main/java/org/mercury_im/messenger/core/MercuryConnection.java @@ -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() { diff --git a/core/src/main/java/org/mercury_im/messenger/core/RosterStore.java b/core/src/main/java/org/mercury_im/messenger/core/RosterStore.java index f10e659..d3c8176 100644 --- a/core/src/main/java/org/mercury_im/messenger/core/RosterStore.java +++ b/core/src/main/java/org/mercury_im/messenger/core/RosterStore.java @@ -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 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>) o -> { + itemMap.clear(); + for (ContactModel c : o) { + rosterRepository.getEntityForContact(c.getId()).subscribeOn(Schedulers.io()) + .subscribe((Consumer) 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) 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 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 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()); } } diff --git a/core/src/main/java/org/mercury_im/messenger/core/di/CenterModule.java b/core/src/main/java/org/mercury_im/messenger/core/di/CenterModule.java index b4fd371..71c0560 100644 --- a/core/src/main/java/org/mercury_im/messenger/core/di/CenterModule.java +++ b/core/src/main/java/org/mercury_im/messenger/core/di/CenterModule.java @@ -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 diff --git a/persistence-room/build.gradle b/persistence-room/build.gradle index 621cca0..2a45bd6 100644 --- a/persistence-room/build.gradle +++ b/persistence-room/build.gradle @@ -27,6 +27,11 @@ android { } } + compileOptions { + sourceCompatibility 1.8 + targetCompatibility 1.8 + } + } dependencies { diff --git a/persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/1.json b/persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/1.json index b449af6..6b2d3d5 100644 --- a/persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/1.json +++ b/persistence-room/schemas/org.mercury_im.messenger.persistence.room.AppDatabase/1.json @@ -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')" ] } } \ No newline at end of file diff --git a/persistence-room/src/androidTest/java/org/mercury_im/messenger/persistence/room/ExampleInstrumentedTest.java b/persistence-room/src/androidTest/java/org/mercury_im/messenger/persistence/room/ExampleInstrumentedTest.java index 72d3add..92e759d 100644 --- a/persistence-room/src/androidTest/java/org/mercury_im/messenger/persistence/room/ExampleInstrumentedTest.java +++ b/persistence-room/src/androidTest/java/org/mercury_im/messenger/persistence/room/ExampleInstrumentedTest.java @@ -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> accounts = accountRepository.getAllAccounts(); diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/AppDatabase.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/AppDatabase.java index d220965..9b5ba26 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/AppDatabase.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/AppDatabase.java @@ -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(); } diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomModule.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomModule.java index 2326923..f41d40c 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomModule.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomModule.java @@ -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 diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomRepositoryModule.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomRepositoryModule.java index 4e64a6c..ab23069 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomRepositoryModule.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/RoomRepositoryModule.java @@ -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 diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/ContactAttributesDao.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/ContactAttributesDao.java deleted file mode 100644 index 215a009..0000000 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/ContactAttributesDao.java +++ /dev/null @@ -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 { - - @Override - @Insert(onConflict = REPLACE) - Single insert(RoomContactAttributes entity); - - @Query("SELECT * FROM contacts WHERE pk_contact_id = :id") - Maybe getContact(long id); - - @Query("SELECT * FROM contacts WHERE fk_entity_id = :entityId") - Maybe 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> getAllContacts(); - - @Query("SELECT contacts.* FROM contacts JOIN entities " + - "WHERE contacts.fk_account_id = :accountId AND jid = :jid") - Maybe getContactByJid(long accountId, EntityBareJid jid); - - @Query("SELECT * FROM contacts WHERE fk_account_id = :accountId") - Observable> getContactsForAccount(long accountId); -} diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/ContactDao.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/ContactDao.java index 7e81c89..a9a13ac 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/ContactDao.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/ContactDao.java @@ -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 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> 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> getAllContactAndEntities(); - - @Query("SELECT * FROM roster_information") - Observable> getAllRosterInformation(); - - @Query("SELECT * FROM roster_information WHERE pk_account_id = :accountId") - Maybe getRosterInformation(long accountId); +public interface ContactDao extends BaseDao { + @Override @Insert(onConflict = REPLACE) - Completable insertRosterInformation(RoomRosterInformation information); + Single insert(RoomContactModel entity); + + @Query("SELECT * FROM contacts WHERE pk_contact_id = :id") + Maybe getContact(long id); + + @Query("SELECT * FROM contacts WHERE fk_entity_id = :entityId") + Maybe getContactForEntityId(long entityId); + + @Query("SELECT * FROM contacts") + Observable> getAllContacts(); + + @Query("SELECT contacts.* FROM contacts JOIN entities " + + "WHERE contacts.fk_account_id = :accountId AND jid = :jid") + Maybe getContactByJid(long accountId, EntityBareJid jid); + + @Query("SELECT * FROM contacts WHERE fk_account_id = :accountId") + Observable> 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 getEntityForContactId(long contactId); } diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/RosterInformationDao.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/RosterInformationDao.java new file mode 100644 index 0000000..ff08a13 --- /dev/null +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/dao/RosterInformationDao.java @@ -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 { + + @Query("SELECT * FROM roster_information") + Observable> getAllRosterInformation(); + + @Query("SELECT * FROM roster_information WHERE pk_account_id = :accountId") + Observable getRosterInformation(long accountId); + + @Insert(onConflict = REPLACE) + Single insertRosterInformation(RoomRosterInformationModel information); +} diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactAttributes.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactAttributes.java deleted file mode 100644 index 7e53220..0000000 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactAttributes.java +++ /dev/null @@ -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; - } -} diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactModel.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactModel.java index 13d09e0..5a9874d 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactModel.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomContactModel.java @@ -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 { +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; } } diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomRosterInformation.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomRosterInformationModel.java similarity index 89% rename from persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomRosterInformation.java rename to persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomRosterInformationModel.java index 7692ba0..2b7cfc5 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomRosterInformation.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/model/RoomRosterInformationModel.java @@ -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"; diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAccountRepository.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAccountRepository.java index 55fa4d3..1dfdf50 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAccountRepository.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IAccountRepository.java @@ -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; diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IChatRepository.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IChatRepository.java index c5a5bf8..c96aa90 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IChatRepository.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IChatRepository.java @@ -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 { return chatDao.getChatWithJid(account.getId(), jid); } - @Override - public Maybe getChatWith(ContactAttributes contact) { - return chatDao.getChatWithContact(contact.getId()); - } - @Override public Maybe getChatWith(ContactModel contact) { - return getChatWith(contact.getContact()); + return chatDao.getChatWithContact(contact.getId()); } @Override diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IContactAttributesRepository.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IContactAttributesRepository.java deleted file mode 100644 index 1d66545..0000000 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IContactAttributesRepository.java +++ /dev/null @@ -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 { - - private final ContactAttributesDao contactAttributesDao; - - public IContactAttributesRepository(ContactAttributesDao dao) { - this.contactAttributesDao = dao; - } - - @Override - public RoomContactAttributes newContactAttributesModel() { - return new RoomContactAttributes(); - } - - @Override - public Observable> getAllContactAttributes() { - return contactAttributesDao.getAllContacts(); - } - - @Override - public Single updateOrInsertContactAttributes(RoomContactAttributes attributes) { - return contactAttributesDao.insert(attributes); - } - - @Override - public Maybe getContactAttributes(long id) { - return contactAttributesDao.getContact(id); - } - - @Override - public Maybe getContactAttributesForEntity(long entityId) { - return contactAttributesDao.getContactForEntityId(entityId); - } - - -} diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IContactRepository.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IContactRepository.java deleted file mode 100644 index ecbc0e8..0000000 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IContactRepository.java +++ /dev/null @@ -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 { - - 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 getContact(long accountId, EntityBareJid jid) { - return dao.getContactAndEntity(accountId, jid); - } - - @Override - public Maybe getContactForEntity(EntityModel entityModel) { - return getContact(entityModel.getAccountId(), entityModel.getJid()); - } - - @Override - public Observable> getAllContactsOfAccount(long accountId) { - return dao.getAllContactAndEntities(accountId); - } - - @Override - public Observable> getAllContacts() { - return dao.getAllContactAndEntities(); - } - - @Override - public Observable> getAllRosterInformation() { - return dao.getAllRosterInformation(); - } - - @Override - public Completable insertRosterInformation(RoomRosterInformation rosterInfo) { - return dao.insertRosterInformation(rosterInfo); - } - - @Override - public Maybe getRosterInformation(long accountId) { - return dao.getRosterInformation(accountId); - } -} diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IEntityRepository.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IEntityRepository.java deleted file mode 100644 index 3007fb9..0000000 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IEntityRepository.java +++ /dev/null @@ -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 { - - private final EntityDao dao; - - @Inject - public IEntityRepository(EntityDao dao) { - this.dao = dao; - } - - @Override - public RoomEntityModel newEntityModel() { - return new RoomEntityModel(); - } - - @Override - public Maybe getEntity(C contact) { - return dao.getEntity(contact.getId()); - } - - @Override - public Maybe getEntity(long accountId, EntityBareJid jid) { - return dao.getEntityFor(accountId, jid); - } -} diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IMessageRepository.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IMessageRepository.java index 143d3a3..164623c 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IMessageRepository.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IMessageRepository.java @@ -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 { private final MessageDao messageDao; + @Inject public IMessageRepository(MessageDao messageDao) { this.messageDao = messageDao; } diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IRosterRepository.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IRosterRepository.java new file mode 100644 index 0000000..bd8bcf5 --- /dev/null +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/repository/IRosterRepository.java @@ -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 { + + 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> 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> 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 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 getEntityForContact(long contactId) { + Single s = contactDao.getEntityForContactId(contactId); + return s; + } + + @Override + public Maybe 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 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 entity = getEntity(accountId, jid.asEntityBareJidOrThrow()); + return entity.flatMapCompletable(new Function() { + @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 getRosterInformationForAccount(long accountId) { + return rosterInformationDao.getRosterInformation(accountId); + } + + @Override + public Single updateRosterInformation(RoomRosterInformationModel rosterInformation) { + return rosterInformationDao.insertRosterInformation(rosterInformation); + } + + /* + RoomEntityModel + */ + + @Override + public RoomEntityModel newEntityModel() { + return new RoomEntityModel(); + } + + @Override + public Maybe getEntity(long id) { + return entityDao.getEntity(id); + } + + @Override + public Maybe getEntity(long accountId, EntityBareJid jid) { + return entityDao.getEntityFor(accountId, jid); + } + + @Override + public Single insertOrReplaceEntity(RoomEntityModel entity) { + return entityDao.insert(entity); + } + +} diff --git a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/type_converter/DirectionConverter.java b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/type_converter/DirectionConverter.java index ead5619..86a3ae2 100644 --- a/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/type_converter/DirectionConverter.java +++ b/persistence-room/src/main/java/org/mercury_im/messenger/persistence/room/type_converter/DirectionConverter.java @@ -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; } } diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/model/ContactAttributes.java b/persistence/src/main/java/org/mercury_im/messenger/persistence/model/ContactAttributes.java deleted file mode 100644 index 3a82600..0000000 --- a/persistence/src/main/java/org/mercury_im/messenger/persistence/model/ContactAttributes.java +++ /dev/null @@ -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 - } -} diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/model/ContactModel.java b/persistence/src/main/java/org/mercury_im/messenger/persistence/model/ContactModel.java index 50ed159..a6957ab 100644 --- a/persistence/src/main/java/org/mercury_im/messenger/persistence/model/ContactModel.java +++ b/persistence/src/main/java/org/mercury_im/messenger/persistence/model/ContactModel.java @@ -1,12 +1,48 @@ package org.mercury_im.messenger.persistence.model; -public interface ContactModel { +public interface ContactModel { - 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 + } } diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ChatRepository.java b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ChatRepository.java index 5554693..0ebb803 100644 --- a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ChatRepository.java +++ b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ChatRepository.java @@ -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 { Maybe getChatWith(EntityModel identity); - Maybe getChatWith(ContactAttributes contact); - Maybe getChatWith(ContactModel contact); Completable closeChat(E chat); diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ContactAttributesRepository.java b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ContactAttributesRepository.java deleted file mode 100644 index 310ba6b..0000000 --- a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ContactAttributesRepository.java +++ /dev/null @@ -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 newContactAttributesModel(); - - Observable> getAllContactAttributes(); - - Single updateOrInsertContactAttributes(E attributes); - - Maybe getContactAttributes(long id); - - Maybe getContactAttributesForEntity(long entityId); -} diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ContactRepository.java b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ContactRepository.java deleted file mode 100644 index e2f6a62..0000000 --- a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/ContactRepository.java +++ /dev/null @@ -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, I extends RosterInformationModel> { - - CE newContactModel(); - - ContactAttributes newAttributesModel(); - - EntityModel newEntityModel(); - - Maybe getContact(long accountId, EntityBareJid jid); - - Maybe getContactForEntity(EntityModel entityModel); - - Observable> getAllContactsOfAccount(long accountId); - - Observable> getAllContacts(); - - Observable> getAllRosterInformation(); - - Maybe getRosterInformation(long accountId); - - Completable insertRosterInformation(I rosterInfo); -} diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/EntityRepository.java b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/EntityRepository.java deleted file mode 100644 index ce041f3..0000000 --- a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/EntityRepository.java +++ /dev/null @@ -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 newEntityModel(); - - Maybe getEntity(C contact); - - Maybe getEntity(long accountId, EntityBareJid jid); - -} diff --git a/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RosterRepository.java b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RosterRepository.java new file mode 100644 index 0000000..ad03179 --- /dev/null +++ b/persistence/src/main/java/org/mercury_im/messenger/persistence/repository/RosterRepository.java @@ -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 { + + /* + 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> 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> 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> 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 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 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 getContactForEntity(long entityId); + + public Maybe 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 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 getRosterInformationForAccount(AccountModel account) { + return getRosterInformationForAccount(account.getId()); + } + + public abstract Single updateRosterInformation(V rosterInformation); + + public Single updateRosterVersionForAccount(long accountID, String rosterVersion) { + V info = newRosterInformationModel(); + info.setAccountId(accountID); + info.setRosterVersion(rosterVersion); + return updateRosterInformation(info); + } + + public Single 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 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 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 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 getEntity(AccountModel account, EntityBareJid jid) { + return getEntity(account.getId(), jid); + } + + public abstract Single getEntityForContact(long contactId); + + public Single 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 insertOrReplaceEntity(E entity); + +}