Change persistence backend to requery (fix #8)

This commit is contained in:
Paul Schaub 2019-10-28 14:56:56 +01:00
parent 5b1d7331e7
commit e5dfa3c030
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
125 changed files with 1836 additions and 4515 deletions

View File

@ -80,8 +80,6 @@
<option value="$PROJECT_DIR$/libs/Smack/smack-xmlparser" />
<option value="$PROJECT_DIR$/libs/Smack/smack-xmlparser-stax" />
<option value="$PROJECT_DIR$/libs/Smack/smack-xmlparser-xpp3" />
<option value="$PROJECT_DIR$/persistence" />
<option value="$PROJECT_DIR$/persistence-room" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />

View File

@ -15,3 +15,10 @@ cd <project-directory>
git submodule init && git submodule update
gradle assembleDebug
```
## FAQ
* I want to develop, but lots of `org.jivesoftware.smackx.*` classes cannot be found!
* You forgot to type `git submodule init && git submodule update` as mentioned above
* I'm missing `org.mercury_im.messenger.persistence.requery.*` classes???
* In Android Studio select the `persistence-requery` module and then click "Build -> Make Module 'persistence-requery'".

View File

@ -64,7 +64,7 @@ task checkstyleAndroidTest(type: Checkstyle) {
}
check.configure {
dependsOn(checkstyleMain)
// dependsOn(checkstyleMain)
// dependsOn(checkstyleTest)
// dependsOn(checkstyleAndroidTest)
}
@ -75,8 +75,8 @@ dependencies {
// Depend on the core project for XMPP related stuff
implementation project(':core')
// Plug a Room based implementation into the persistence api
implementation project(':persistence-room')
implementation "io.requery:requery-android:$requeryVersion"
implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41'
// Dagger 2 for dependency injection
implementation "com.google.dagger:dagger:$daggerVersion"
@ -86,6 +86,9 @@ dependencies {
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycleVersion"
annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycleVersion"
// Android extension for rxJava
api "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
// ButterKnife for View Binding
implementation "com.jakewharton:butterknife:$butterKnifeVersion"
annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnifeVersion"

View File

@ -0,0 +1,149 @@
package org.mercury_im.messenger;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.persistence.requery.entity.AccountModel;
import org.mercury_im.messenger.persistence.requery.entity.ChatModel;
import org.mercury_im.messenger.persistence.requery.entity.ContactModel;
import org.mercury_im.messenger.persistence.requery.entity.EntityModel;
import org.mercury_im.messenger.persistence.requery.entity.LastReadChatMessageRelation;
import org.mercury_im.messenger.persistence.requery.entity.MessageModel;
import org.mercury_im.messenger.persistence.requery.entity.Models;
import org.mercury_im.messenger.core.requery.enums.SubscriptionDirection;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import io.requery.Persistable;
import io.requery.android.sqlite.DatabaseSource;
import io.requery.reactivex.ReactiveEntityStore;
import io.requery.reactivex.ReactiveSupport;
import io.requery.sql.Configuration;
import io.requery.sql.EntityDataStore;
import io.requery.sql.TableCreationMode;
@RunWith(AndroidJUnit4.class)
public class RequeryDatabaseTest {
static ReactiveEntityStore<Persistable> dataStore;
@BeforeClass
public static void setup() {
Context appContext = InstrumentationRegistry.getTargetContext();
DatabaseSource source = new DatabaseSource(appContext, Models.DEFAULT,
"mercury_test_db", 1);
// use this in development mode to drop and recreate the tables on every upgrade
source.setTableCreationMode(TableCreationMode.DROP_CREATE);
source.setLoggingEnabled(true);
Configuration configuration = source.getConfiguration();
dataStore = ReactiveSupport.toReactiveStore(
new EntityDataStore<>(configuration));
}
@Test
public void databaseTest() throws InterruptedException {
AccountModel accountModel = new AccountModel();
accountModel.setJid(JidCreate.entityBareFromOrThrowUnchecked("juliet@capulet.lit"));
accountModel.setPassword("romeo0romeo");
accountModel.setRosterVersion(null);
accountModel.setEnabled(true);
Disposable accounts = dataStore.select(AccountModel.class).get().observableResult().subscribe(
accountModels -> accountModels.forEach(System.out::println));
Disposable entities = dataStore.select(EntityModel.class).get().observableResult()
.subscribeOn(Schedulers.io())
.subscribe(entityModels -> entityModels.forEach(System.out::println));
Disposable contacts = dataStore.select(ContactModel.class).get()
.observableResult().subscribeOn(Schedulers.io()).subscribe(
contactModels -> contactModels.forEach(System.out::println));
Thread.sleep(100);
dataStore.upsert(accountModel).subscribeOn(Schedulers.io())
.subscribe();
ContactModel contactModel = new ContactModel();
EntityModel entityModel = new EntityModel();
entityModel.setAccount(accountModel);
entityModel.setJid(JidCreate.entityBareFromOrThrowUnchecked("romeo@capulet.lit"));
contactModel.setEntity(entityModel);
contactModel.setRostername("Romeo");
contactModel.setSub_direction(SubscriptionDirection.both);
dataStore.insert(contactModel).blockingGet();
dataStore.select(AccountModel.ENABLED, ContactModel.ROSTERNAME)
.from(AccountModel.class)
.join(EntityModel.class).on(AccountModel.ID.eq(EntityModel.ACCOUNT_ID))
.join(ContactModel.class).on(EntityModel.ID.eq(ContactModel.ENTITY_ID))
.get().observableResult().blockingForEach(e -> e.forEach(System.out::println));
Thread.sleep(10000);
accounts.dispose();
entities.dispose();
contacts.dispose();
}
public static class ContactDetail {
private boolean enabled;
private String rostername;
}
@Test
public void test2() {
AccountModel account = new AccountModel();
account.setJid(JidCreate.entityBareFromOrThrowUnchecked("omemouser@jabberhead.tk"));
account.setEnabled(true);
account.setPassword("nöö");
EntityModel entity = new EntityModel();
entity.setJid(JidCreate.entityBareFromOrThrowUnchecked("jabbertest@test.test"));
entity.setAccount(account);
ContactModel contact = new ContactModel();
contact.setEntity(entity);
contact.setRostername("Olaf");
contact.setSub_direction(SubscriptionDirection.both);
contact.setSub_approved(false);
contact.setSub_pending(true);
dataStore.upsert(contact).blockingGet();
ChatModel chat = new ChatModel();
chat.setPeer(entity);
chat.setDisplayed(true);
dataStore.upsert(chat).blockingGet();
MessageModel message = new MessageModel();
message.setBody("Hallo Welt!");
message.setChat(chat);
dataStore.upsert(message).blockingGet();
LastReadChatMessageRelation lastRead = new LastReadChatMessageRelation();
lastRead.setChat(chat);
lastRead.setMessage(message);
dataStore.upsert(lastRead).blockingGet();
MessageModel message2 = new MessageModel();
message2.setChat(chat);
message2.setBody("How are you?");
dataStore.upsert(message2).blockingGet();
LastReadChatMessageRelation lastRead2 = new LastReadChatMessageRelation();
lastRead2.setChat(chat);
lastRead2.setMessage(message2);
dataStore.upsert(lastRead2).blockingGet();
}
}

View File

@ -1,5 +1,6 @@
package org.mercury_im.messenger;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Application;
import android.app.NotificationChannel;
@ -10,12 +11,11 @@ import android.os.Build;
import org.mercury_im.messenger.core.centers.ConnectionCenter;
import org.mercury_im.messenger.core.connection.MercuryConfiguration;
import org.mercury_im.messenger.persistence.util.ChatAndPossiblyContact;
import org.mercury_im.messenger.core.util.ContactNameUtil;
import org.mercury_im.messenger.di.component.AppComponent;
import org.mercury_im.messenger.di.component.DaggerAppComponent;
import org.mercury_im.messenger.di.module.AppModule;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.room.RoomModule;
import org.mercury_im.messenger.persistence.room.RoomRepositoryModule;
import org.mercury_im.messenger.service.XmppConnectionService;
import org.mercury_im.messenger.util.AbstractActivityLifecycleCallbacks;
@ -38,6 +38,7 @@ public class MercuryImApplication extends Application implements org.mercury_im.
// Keep track of activities in "started" state.
// This will come in handy for CSI
// see https://medium.com/@iamsadesh/android-how-to-detect-when-app-goes-background-foreground-fd5a4d331f8a
private AtomicInteger activityReferences = new AtomicInteger(0);
private AtomicBoolean isActivityChangingConfiguration = new AtomicBoolean(false);
@ -78,8 +79,6 @@ public class MercuryImApplication extends Application implements org.mercury_im.
public AppComponent createAppComponent() {
AppComponent appComponent = DaggerAppComponent.builder()
.appModule(new AppModule(this))
.roomModule(new RoomModule(this))
.roomRepositoryModule(new RoomRepositoryModule())
.build();
appComponent.inject(this);
@ -100,6 +99,7 @@ public class MercuryImApplication extends Application implements org.mercury_im.
String fName = getResources().getString(R.string.channel_name_foreground);
String fDescription = getResources().getString(R.string.channel_description_foreground);
@SuppressLint("WrongConstant")
NotificationChannel foreground = new NotificationChannel(Notifications.NOTIFICATION_CHANNEL__FOREGROUND_SERVICE,
fName, NotificationManager.IMPORTANCE_MIN);
foreground.setDescription(fDescription);
@ -110,6 +110,7 @@ public class MercuryImApplication extends Application implements org.mercury_im.
String mName = getResources().getString(R.string.channel_name_message);
String mDescription = getResources().getString(R.string.channel_description_message);
@SuppressLint("WrongConstant")
NotificationChannel messages = new NotificationChannel(Notifications.NOTIFICATION_CHANNEL__NEW_MESSAGE,
mName, NotificationManager.IMPORTANCE_DEFAULT);
messages.setDescription(mDescription);
@ -117,8 +118,10 @@ public class MercuryImApplication extends Application implements org.mercury_im.
}
@Override
public int chatMessageReceived(Chat chat, String contactName, String body) {
return Notifications.chatMessageReceived(this, chat, contactName, body);
public int chatMessageReceived(ChatAndPossiblyContact chatAndPossiblyContact, String body) {
return Notifications.chatMessageReceived(this,
chatAndPossiblyContact.getChat(),
ContactNameUtil.displayableNameFrom(chatAndPossiblyContact.getContact()), body);
}
public AppComponent getAppComponent() {

View File

@ -7,7 +7,7 @@ import android.content.Intent;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.entity.ChatModel;
import org.mercury_im.messenger.ui.chat.ChatActivity;
public class Notifications {
@ -20,14 +20,14 @@ public class Notifications {
// Notification IDs
public static final int FOREGROUND_SERVICE_ID = 1; // must not be 0
public static int chatMessageReceived(Context context, Chat chat, String contactName, String body) {
public static int chatMessageReceived(Context context, ChatModel chat, String contactName, String body) {
NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(context);
int id = (chat.accountId + " " + chat.jid.toString()).hashCode();
int id = (int) chat.getId();
Intent tapAction = new Intent(context, ChatActivity.class);
tapAction.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
tapAction.putExtra(ChatActivity.EXTRA_JID, chat.jid.toString());
tapAction.putExtra(ChatActivity.EXTRA_ACCOUNT, chat.accountId);
tapAction.putExtra(ChatActivity.EXTRA_JID, chat.getPeer().getJid().toString());
tapAction.putExtra(ChatActivity.EXTRA_ACCOUNT, chat.getPeer().getAccount().getId());
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, tapAction, 0);
NotificationCompat.Builder builder = new NotificationCompat.Builder(context,

View File

@ -1,9 +1,10 @@
package org.mercury_im.messenger.di.component;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.core.di.XmppComponent;
import org.mercury_im.messenger.core.stores.PlainMessageStore;
import org.mercury_im.messenger.di.module.AppModule;
import org.mercury_im.messenger.persistence.room.RoomModule;
import org.mercury_im.messenger.di.module.AndroidPersistenceModule;
import org.mercury_im.messenger.service.XmppConnectionService;
import org.mercury_im.messenger.ui.MainActivity;
import org.mercury_im.messenger.ui.chat.ChatActivity;
@ -25,10 +26,11 @@ import dagger.Component;
* application.
*/
@Singleton
@Component(modules = {
AppModule.class,
RoomModule.class
})
@Component(
modules = {
AppModule.class,
AndroidPersistenceModule.class
})
public interface AppComponent {
// Application

View File

@ -0,0 +1,57 @@
package org.mercury_im.messenger.di.module;
import android.app.Application;
import org.mercury_im.messenger.BuildConfig;
import org.mercury_im.messenger.persistence.entity.Models;
import org.mercury_im.messenger.thread_utils.ThreadUtils;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
import io.requery.Persistable;
import io.requery.android.sqlite.DatabaseSource;
import io.requery.reactivex.ReactiveEntityStore;
import io.requery.reactivex.ReactiveSupport;
import io.requery.sql.Configuration;
import io.requery.sql.EntityDataStore;
import io.requery.sql.TableCreationMode;
@Module
public class AndroidPersistenceModule {
@Provides
@Singleton
static ReactiveEntityStore<Persistable> provideDatabase(Application application) {
// override onUpgrade to handle migrating to a new version
DatabaseSource source = new DatabaseSource(application, Models.DEFAULT,
"mercury_req_db", 1);
if (BuildConfig.DEBUG) {
// use this in development mode to drop and recreate the tables on every upgrade
source.setTableCreationMode(TableCreationMode.DROP_CREATE);
}
Configuration configuration = source.getConfiguration();
ReactiveEntityStore<Persistable> dataStore = ReactiveSupport.toReactiveStore(
new EntityDataStore<>(configuration));
return dataStore;
}
@Provides
@Named(value = ThreadUtils.SCHEDULER_IO)
@Singleton
static Scheduler provideDatabaseThread() {
return Schedulers.io();
}
@Provides
@Named(value = ThreadUtils.SCHEDULER_UI)
@Singleton
static Scheduler providerUIThread() {
return AndroidSchedulers.mainThread();
}
}

View File

@ -8,10 +8,14 @@ import dagger.Provides;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.core.NotificationManager;
import org.mercury_im.messenger.core.di.CenterModule;
import org.mercury_im.messenger.persistence.di.RequeryModule;
import javax.inject.Singleton;
@Module(includes = CenterModule.class)
@Module(includes = {
CenterModule.class,
RequeryModule.class
})
public class AppModule {
private MercuryImApplication mApplication;

View File

@ -15,11 +15,10 @@ import com.google.android.material.navigation.NavigationView;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.ui.chatlist.ChatListFragment;
import org.mercury_im.messenger.ui.login.AccountsFragment;
import org.mercury_im.messenger.ui.login.LoginActivity;
import org.mercury_im.messenger.ui.roster.RosterFragment;
import org.mercury_im.messenger.ui.settings.SettingsActivity;

View File

@ -10,6 +10,7 @@ import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@ -22,6 +23,7 @@ import io.reactivex.schedulers.Schedulers;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
@ -91,7 +93,7 @@ public class ChatActivity extends AppCompatActivity
getSupportActionBar().setSubtitle(jid.asUnescapedString());
accountId = savedInstanceState.getLong(EXTRA_ACCOUNT);
chatViewModel = ViewModelProviders.of(this).get(ChatViewModel.class);
chatViewModel = new ViewModelProvider(this).get(ChatViewModel.class);
chatViewModel.init(accountId, jid);
// Listen for updates to contact information and messages
observeViewModel(chatViewModel);

View File

@ -4,19 +4,13 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import io.reactivex.Completable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.core.centers.ConnectionCenter;
import org.mercury_im.messenger.core.util.ContactNameUtil;
import org.mercury_im.messenger.persistence.model.ChatModel;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.model.MessageModel;
import org.mercury_im.messenger.persistence.entity.ChatModel;
import org.mercury_im.messenger.persistence.entity.ContactModel;
import org.mercury_im.messenger.persistence.entity.EntityModel;
import org.mercury_im.messenger.persistence.entity.MessageModel;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.persistence.repository.MessageRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
@ -25,6 +19,10 @@ import java.util.List;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
public class ChatViewModel extends ViewModel {
private final CompositeDisposable disposable = new CompositeDisposable();
@ -41,9 +39,7 @@ public class ChatViewModel extends ViewModel {
@Inject
ConnectionCenter connectionCenter;
private long accountId;
private EntityBareJid jid;
private MutableLiveData<EntityModel> entity = new MutableLiveData<>();
private MutableLiveData<ContactModel> contact = new MutableLiveData<>();
private MutableLiveData<List<MessageModel>> messages = new MutableLiveData<>();
private MutableLiveData<String> contactDisplayName = new MutableLiveData<>();
@ -55,29 +51,24 @@ public class ChatViewModel extends ViewModel {
}
public void init(long accountId, EntityBareJid jid) {
this.accountId = accountId;
this.jid = jid;
disposable.add(rosterRepository.getOrCreateEntity(accountId, jid)
.subscribe((Consumer<EntityModel>) this::init));
}
disposable.add(rosterRepository.getContact(accountId, jid)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<ContactModel>)
contactModel -> {
ChatViewModel.this.contact.setValue(contactModel);
contactDisplayName.setValue(ContactNameUtil.displayableNameFrom(contactModel));
public void init(EntityModel entityModel) {
disposable.add(rosterRepository.getContact(entityModel.getAccount().getId(), entityModel.getJid())
.subscribe(reactiveResult -> {
ContactModel model = reactiveResult.first();
ChatViewModel.this.contact.setValue(model);
contactDisplayName.setValue(model.getRostername());
}));
disposable.add(messageRepository.getAllMessagesOfChat(accountId, jid)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<MessageModel>>)
messages -> ChatViewModel.this.messages.setValue(messages)));
disposable.add(chatRepository.getOrCreateChatWith(accountId, jid)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<ChatModel>)
chatModel -> this.chat.setValue(chatModel)));
disposable.add(messageRepository.getAllMessagesOfEntity(entityModel)
.subscribe(reactiveResult -> {
List<MessageModel> messages = reactiveResult.toList();
ChatViewModel.this.messages.setValue(messages);
}));
}
@Override
@ -99,6 +90,7 @@ public class ChatViewModel extends ViewModel {
}
public void queryTextChanged(String query) {
/*
if (query.isEmpty()) {
disposable.add(messageRepository.getAllMessagesOfChat(accountId, jid)
.subscribeOn(Schedulers.io())
@ -112,6 +104,8 @@ public class ChatViewModel extends ViewModel {
.subscribe((Consumer<List<MessageModel>>) o -> {
messages.setValue(o);
}));
*/
}
public Completable requestMamMessages() {

View File

@ -10,7 +10,7 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.MessageModel;
import org.mercury_im.messenger.persistence.entity.MessageModel;
import org.mercury_im.messenger.ui.util.MessageBackgroundDrawable;
import java.util.ArrayList;

View File

@ -3,16 +3,20 @@ package org.mercury_im.messenger.ui.chatlist;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.view.ActionMode;
import androidx.recyclerview.widget.RecyclerView;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.entity.ChatModel;
import org.mercury_im.messenger.ui.chat.ChatActivity;
import org.mercury_im.messenger.ui.util.AbstractRecyclerViewAdapter;
import org.mercury_im.messenger.util.ColorUtil;
@ -21,7 +25,7 @@ import butterknife.BindView;
import butterknife.ButterKnife;
public class ChatListRecyclerViewAdapter
extends AbstractRecyclerViewAdapter<Chat, ChatListRecyclerViewAdapter.ChatHolder> {
extends AbstractRecyclerViewAdapter<ChatModel, ChatListRecyclerViewAdapter.ChatHolder> {
public ChatListRecyclerViewAdapter() {
super(new ChatMessageDiffCallback(true));
@ -37,20 +41,24 @@ public class ChatListRecyclerViewAdapter
@Override
public void onBindViewHolder(@NonNull ChatHolder holder, int position) {
Chat model = getModelAt(position);
holder.nameView.setText(model.contactName != null ?
model.contactName : model.jid.toString());
holder.avatarView.setColorFilter(ColorUtil.consistentColor(model.jid.toString()));
ChatModel model = getModelAt(position);
holder.nameView.setText(model.getPeer().getJid() != null ?
model.getPeer().getJid() : model.toString());
holder.avatarView.setColorFilter(ColorUtil.consistentColor(model.getPeer().getJid().toString()));
holder.itemView.setOnClickListener(view -> {
Intent intent = new Intent(holder.context, ChatActivity.class);
intent.putExtra(ChatActivity.EXTRA_JID, model.jid.toString());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, model.accountId);
intent.putExtra(ChatActivity.EXTRA_JID, model.getPeer().getJid().toString());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, model.getPeer().getAccount().getId());
holder.context.startActivity(intent);
});
// TODO: Better bindable model pls
holder.itemView.setOnLongClickListener(v -> {
((AppCompatActivity) v.getContext()).startSupportActionMode(actionModeCallback);
return true;
});
}
public static class ChatHolder extends RecyclerView.ViewHolder {
@ -76,20 +84,49 @@ public class ChatListRecyclerViewAdapter
}
}
private static class ChatMessageDiffCallback extends AbstractDiffCallback<Chat> {
private static class ChatMessageDiffCallback extends AbstractDiffCallback<ChatModel> {
ChatMessageDiffCallback(boolean detectMoves) {
super(detectMoves);
}
@Override
public boolean areItemsTheSame(Chat oldItem, Chat newItem) {
return oldItem.chatId == newItem.chatId;
public boolean areItemsTheSame(ChatModel oldItem, ChatModel newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(Chat oldItem, Chat newItem) {
public boolean areContentsTheSame(ChatModel oldItem, ChatModel newItem) {
return areItemsTheSame(oldItem, newItem);
}
}
private androidx.appcompat.view.ActionMode.Callback actionModeCallback = new androidx.appcompat.view.ActionMode.Callback() {
private boolean multiSelect = false;
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.getMenuInflater().inflate(R.menu.actionmode_chatlist, menu);
multiSelect = true;
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
mode.finish();
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
notifyDataSetChanged();
}
};
}

View File

@ -4,14 +4,8 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.persistence.model.MessageModel;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.entity.ChatModel;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.persistence.repository.MessageRepository;
@ -19,6 +13,8 @@ import java.util.List;
import javax.inject.Inject;
import io.reactivex.disposables.CompositeDisposable;
public class ChatListViewModel extends ViewModel {
@Inject
@ -29,27 +25,16 @@ public class ChatListViewModel extends ViewModel {
private CompositeDisposable disposable = new CompositeDisposable();
private final MutableLiveData<List<Chat>> chats = new MutableLiveData<>();
private final MutableLiveData<List<ChatModel>> chats = new MutableLiveData<>();
public ChatListViewModel() {
MercuryImApplication.getApplication().getAppComponent().inject(this);
disposable.add(chatRepository.getDisplayableChats()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<Chat>>) chats::setValue));
disposable.add(messageRepository.getAllMessages()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<MessageModel>>) messageModels -> {
for (MessageModel message : messageModels) {
}
}));
disposable.add(chatRepository.getVisibleChats()
.subscribe(result -> chats.setValue(result.toList())));
}
public LiveData<List<Chat>> getChats() {
public LiveData<List<ChatModel>> getChats() {
return chats;
}

View File

@ -17,7 +17,7 @@ import com.google.android.material.floatingactionbutton.ExtendedFloatingActionBu
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;
import butterknife.BindView;
import butterknife.ButterKnife;

View File

@ -11,11 +11,9 @@ import android.widget.TextView;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import de.hdodenhof.circleimageview.CircleImageView;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;
import org.mercury_im.messenger.ui.login.AccountsFragment.OnAccountListItemClickListener;
import org.mercury_im.messenger.util.AbstractDiffCallback;
import org.mercury_im.messenger.util.ColorUtil;
@ -47,9 +45,8 @@ public class AccountsRecyclerViewAdapter extends RecyclerView.Adapter<AccountsRe
AccountModel account = mValues.get(position);
holder.jid.setText(account.getJid());
holder.avatar.setColorFilter(ColorUtil.consistentColor(account.getJid().toString()));
holder.enabled.setChecked(account.getEnabled());
holder.enabled.setChecked(account.isEnabled());
holder.accountModel = account;
holder.status.setText(account.getState());
holder.enabled.setOnCheckedChangeListener((compoundButton, b) -> {
viewModel.toggleAccountEnabled(account);
});

View File

@ -6,25 +6,19 @@ import android.widget.Toast;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import io.reactivex.Scheduler;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import org.jivesoftware.smack.XMPPConnection;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.core.centers.ConnectionCenter;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.disposables.CompositeDisposable;
public class AccountsViewModel extends AndroidViewModel {
@Inject
@ -40,10 +34,8 @@ public class AccountsViewModel extends AndroidViewModel {
public AccountsViewModel(Application application) {
super(application);
MercuryImApplication.getApplication().getAppComponent().inject(this);
compositeDisposable.add(repository.getAllAccounts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<AccountModel>>) accounts::setValue));
compositeDisposable.add(repository.getAll()
.subscribe(accountModels -> accounts.setValue(accountModels.toList())));
}
@Override
@ -63,9 +55,8 @@ public class AccountsViewModel extends AndroidViewModel {
return;
}
accountModel.setEnabled(!accountModel.getEnabled());
repository.updateAccount(accountModel)
.subscribeOn(Schedulers.io())
accountModel.setEnabled(!accountModel.isEnabled());
repository.upsert(accountModel)
.subscribe();
}
}

View File

@ -9,18 +9,18 @@ import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProviders;
import butterknife.BindView;
import butterknife.ButterKnife;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.textfield.TextInputEditText;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;
import org.mercury_im.messenger.util.TextChangedListener;
import butterknife.BindView;
import butterknife.ButterKnife;
/**
* A login screen that offers login via email/password.
@ -49,7 +49,7 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
// Set up the login form.
ButterKnife.bind(this);
viewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
viewModel = new ViewModelProvider(this).get(LoginViewModel.class);
observeViewModel(viewModel);
displayCredentials(viewModel.getAccount());
@ -121,7 +121,7 @@ public class LoginActivity extends AppCompatActivity implements TextView.OnEdito
});
}
private void displayCredentials(LiveData<? extends AccountModel> account) {
private void displayCredentials(LiveData<AccountModel> account) {
account.observe(this, accountModel -> {
if (accountModel == null) {
return;

View File

@ -11,17 +11,14 @@ import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.core.centers.ConnectionCenter;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
import javax.inject.Inject;
@ -46,7 +43,7 @@ public class LoginViewModel extends ViewModel {
public LoginViewModel() {
super();
MercuryImApplication.getApplication().getAppComponent().inject(this);
init(accountRepository.newAccountModel());
init(new AccountModel());
}
public LiveData<Boolean> getSigninSuccessful() {
@ -109,7 +106,7 @@ public class LoginViewModel extends ViewModel {
public void login() {
AccountModel account = getAccount().getValue();
if (account != null && account.getJid() != null && !TextUtils.isEmpty(account.getPassword())) {
accountRepository.insertAccount(account);
accountRepository.upsert(account);
}
}
@ -132,18 +129,15 @@ public class LoginViewModel extends ViewModel {
}
if (loginIntact) {
RoomAccountModel accountModel = new RoomAccountModel();
AccountModel accountModel = new AccountModel();
accountModel.setEnabled(true);
accountModel.setJid(bareJid);
accountModel.setPassword(password);
Single<Long> id = accountRepository.insertAccount(accountModel);
id.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new DisposableSingleObserver<Long>() {
Single<AccountModel> insert = accountRepository.upsert(accountModel);
insert.subscribe(new DisposableSingleObserver<AccountModel>() {
@Override
public void onSuccess(Long aLong) {
accountModel.setId(aLong);
Log.d(MercuryImApplication.TAG, "LoginActivity.loginDetailsEntered: Account " + aLong + " inserted.");
public void onSuccess(AccountModel inserted) {
Log.d(MercuryImApplication.TAG, "LoginActivity.loginDetailsEntered: Account " + inserted.getId() + " inserted.");
connectionCenter.createConnection(accountModel);
signinSuccessful.setValue(true);
}

View File

@ -6,17 +6,17 @@ import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.repository.IRosterRepository;
import org.mercury_im.messenger.persistence.entity.ContactModel;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import javax.inject.Inject;
public class ContactListItemViewModel extends AndroidViewModel {
@Inject
IRosterRepository contactRepository;
RosterRepository contactRepository;
private LiveData<RoomContactModel> contact;
private LiveData<ContactModel> contact;
@Inject
public ContactListItemViewModel(@NonNull Application application) {

View File

@ -11,22 +11,20 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import de.hdodenhof.circleimageview.CircleImageView;
import org.jivesoftware.smackx.colors.ConsistentColor;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.R;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.entity.ContactModel;
import org.mercury_im.messenger.ui.chat.ChatActivity;
import org.mercury_im.messenger.ui.util.AbstractRecyclerViewAdapter;
import org.mercury_im.messenger.util.ColorUtil;
import java.util.Objects;
import butterknife.BindView;
import butterknife.ButterKnife;
public class ContactListRecyclerViewAdapter
extends AbstractRecyclerViewAdapter<RoomContactModel, ContactListRecyclerViewAdapter.RosterItemViewHolder> {
extends AbstractRecyclerViewAdapter<ContactModel, ContactListRecyclerViewAdapter.RosterItemViewHolder> {
public ContactListRecyclerViewAdapter() {
super(new ContactDiffCallback());
@ -41,7 +39,7 @@ public class ContactListRecyclerViewAdapter
@Override
public void onBindViewHolder(@NonNull RosterItemViewHolder holder, int position) {
RoomContactModel model = getModelAt(position);
ContactModel model = getModelAt(position);
holder.bind(model);
}
@ -67,8 +65,8 @@ public class ContactListRecyclerViewAdapter
ButterKnife.bind(this, view);
}
void bind(RoomContactModel contactModel) {
String name = contactModel.getRosterName();
void bind(ContactModel contactModel) {
String name = contactModel.getRostername();
nameView.setText(name != null ? name : contactModel.getEntity().getJid().getLocalpart().asUnescapedString());
EntityBareJid jid = contactModel.getEntity().getJid();
jidView.setText(jid.toString());
@ -77,28 +75,28 @@ public class ContactListRecyclerViewAdapter
Intent intent = new Intent(context, ChatActivity.class);
intent.putExtra(ChatActivity.EXTRA_JID, jid.toString());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, contactModel.getEntity().getAccountId());
intent.putExtra(ChatActivity.EXTRA_ACCOUNT, contactModel.getEntity().getAccount().getId());
context.startActivity(intent);
});
}
}
private static class ContactDiffCallback extends AbstractDiffCallback<RoomContactModel> {
private static class ContactDiffCallback extends AbstractDiffCallback<ContactModel> {
ContactDiffCallback() {
super(true);
}
@Override
public boolean areItemsTheSame(RoomContactModel oldItem, RoomContactModel newItem) {
public boolean areItemsTheSame(ContactModel oldItem, ContactModel newItem) {
return oldItem.getId() == newItem.getId();
}
@Override
public boolean areContentsTheSame(RoomContactModel oldItem, RoomContactModel newItem) {
public boolean areContentsTheSame(ContactModel oldItem, ContactModel newItem) {
return areItemsTheSame(oldItem, newItem) &&
Objects.equals(oldItem.getRosterName(), newItem.getRosterName());
Objects.equals(oldItem.getRostername(), newItem.getRostername());
}
}
}

View File

@ -6,27 +6,25 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
import org.mercury_im.messenger.MercuryImApplication;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.entity.ContactModel;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.schedulers.Schedulers;
public class ContactListViewModel extends ViewModel {
@Inject
RosterRepository rosterRepository;
private final MutableLiveData<List<RoomContactModel>> rosterEntryList = new MutableLiveData<>();
private final MutableLiveData<List<ContactModel>> rosterEntryList = new MutableLiveData<>();
private final CompositeDisposable compositeDisposable = new CompositeDisposable();
public ContactListViewModel() {
@ -37,9 +35,10 @@ public class ContactListViewModel extends ViewModel {
compositeDisposable.add(rosterRepository.getAllContacts()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe((Consumer<List<? extends ContactModel>>) o -> {
Log.d("ContactListViewModel", "Room changed contacts: " + o.size());
rosterEntryList.setValue((List<RoomContactModel>) o);
.subscribe(o -> {
List<ContactModel> list = o.toList();
Log.d("ContactListViewModel", "Room changed contacts: " + list.size());
rosterEntryList.setValue(list);
}));
}
@ -49,7 +48,7 @@ public class ContactListViewModel extends ViewModel {
compositeDisposable.clear();
}
public LiveData<List<RoomContactModel>> getRosterEntryList() {
public LiveData<List<ContactModel>> getRosterEntryList() {
return rosterEntryList;
}
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_copy"
android:title="@string/action_copy"
android:icon="@drawable/ic_content_copy_black_24dp" />
<item android:id="@+id/action_reply_message"
android:title="@string/action_reply_message"
android:icon="@drawable/ic_reply_black_24dp" />
</menu>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_close_chat"
android:title="@string/action_close_chat"
android:icon="@drawable/ic_delete_black_24dp" />
</menu>

View File

@ -129,4 +129,6 @@
<string name="action_add_account">Add Account</string>
<string name="action_add_contact">Add Contact</string>
<string name="action_add_bookmark">Add Bookmark</string>
<string name="action_close_chat">Close Chat</string>
<string name="action_copy">Copy</string>
</resources>

View File

@ -21,6 +21,7 @@
<item name="messageBubbleColor">@color/white</item>
<item name="messageTextColor">@color/textBlack</item>
<item name="windowActionModeOverlay">true</item>
</style>
<!-- Light Theme -->

View File

@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:3.5.1'
// NOTE: Do not place your application dependencies here; they belong

View File

@ -1,7 +1,14 @@
apply plugin: 'java-library'
// Add the generated folder to the source directories so that we can work with generated classes
// This is apparently necessary for use with requery.
sourceSets {
main.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/main/"
}
dependencies {
api project(":thread_utils")
api project(":persistence")
// Smack
@ -17,10 +24,19 @@ dependencies {
// api "org.igniterealtime.smack:smack-openpgp:$smackOpenpgpVersion"
// api "org.igniterealtime.smack:smack-resolver-minidns:$smackResolverMiniDnsVersion"
// RxJava2
api "io.reactivex.rxjava2:rxjava:$rxJava2Version"
// Dagger 2 for dependency injection
implementation "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
// Requery ORM
api "io.requery:requery:$requeryVersion"
annotationProcessor "io.requery:requery-processor:$requeryVersion"
// JUnit for testing
testImplementation "junit:junit:$junitVersion"
}

View File

@ -1,9 +1,10 @@
package org.mercury_im.messenger.core;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.util.ChatAndPossiblyContact;
public interface NotificationManager {
int chatMessageReceived(Chat chat, String contactName, String body);
int chatMessageReceived(ChatAndPossiblyContact chatAndPossiblyContact, String body);
}

View File

@ -3,26 +3,25 @@ package org.mercury_im.messenger.core.centers;
import org.jivesoftware.smack.AbstractXMPPConnection;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.tcp.XMPPTCPConnection;
import org.jivesoftware.smack.tcp.XMPPTCPConnectionConfiguration;
import org.jivesoftware.smackx.caps.EntityCapsManager;
import org.jivesoftware.smackx.csi.ClientStateIndicationManager;
import org.jivesoftware.smackx.mam.MamManager;
import org.mercury_im.messenger.core.stores.EntityCapsStore;
import org.mercury_im.messenger.core.connection.MercuryConfiguration;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.core.stores.PlainMessageStore;
import org.mercury_im.messenger.core.stores.RosterStore;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.model.ChatModel;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.entity.AccountModel;
import org.mercury_im.messenger.persistence.entity.ChatModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.core.stores.EntityCapsStore;
import org.mercury_im.messenger.core.stores.PlainMessageStore;
import org.mercury_im.messenger.core.stores.RosterStore;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
@ -34,7 +33,6 @@ import javax.inject.Singleton;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.disposables.Disposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
@Singleton
@ -44,9 +42,9 @@ public class ConnectionCenter {
// Injected
private final AccountRepository accountRepository;
private final RosterRepository rosterRepository;
private final PlainMessageStore messageStore;
private final EntityCapsStore entityCapsStore;
private final RosterRepository rosterRepository;
// Connections
private final Map<Long, MercuryConnection> connectionMap =
@ -86,15 +84,14 @@ public class ConnectionCenter {
}
// otherwise subscribe to accounts and create connections.
Disposable allAccounts = accountRepository.getAllAccounts()
.observeOn(Schedulers.io())
.subscribeOn(Schedulers.computation())
.subscribe((Consumer<List<? extends AccountModel>>) accounts -> {
Disposable allAccounts = accountRepository.getAll()
.observeOn(Schedulers.newThread())
.subscribe(accounts -> {
LOGGER.log(Level.INFO, "Accounts changed.");
Set<Long> accountIds = new HashSet<>();
// Add missing connections to the map
for (AccountModel account : accounts) {
for (AccountModel account : accounts.toList()) {
accountIds.add(account.getId());
if (connectionMap.get(account.getId()) != null) {
continue;
@ -121,7 +118,7 @@ public class ConnectionCenter {
for (AccountModel account : accounts) {
MercuryConnection connection = connectionMap.get(account.getId());
if (account.getEnabled()) {
if (account.isEnabled()) {
if (connection.getConnection().isConnected()) {
continue;
}
@ -139,7 +136,6 @@ public class ConnectionCenter {
});
disposable.add(allAccounts);
}
public MercuryConnection getConnection(AccountModel account) {
@ -179,15 +175,13 @@ public class ConnectionCenter {
public void initializeConnection(MercuryConnection connection) {
// Register roster store
RosterStore rosterStore = new RosterStore(rosterRepository);
RosterStore rosterStore = new RosterStore(rosterRepository, accountRepository);
rosterStore.setAccountId(connection.getAccountId());
rosterStore.subscribe();
connection.getRoster().setRosterStore(rosterStore);
// Register message store
messageStore.registerForMercuryConnection(connection);
//
}
/**
@ -229,21 +223,23 @@ public class ConnectionCenter {
}
public void requestMamMessagesFor(ChatModel chat) {
disposable.add(rosterRepository.getEntity(chat.getPeerEntityId())
.subscribe((Consumer<EntityModel>) entity -> {
MercuryConnection connection = connectionMap.get(entity.getAccountId());
if (connection == null) return;
MamManager mamManager = MamManager.getInstanceFor(connection.getConnection());
MamManager.MamQuery query;
//if (chat.getEarliestMamMessageId() == null) {
query = mamManager.queryMostRecentPage(entity.getJid(), 100);
//} else {
//MamManager.MamQueryArgs queryArgs = MamManager.MamQueryArgs.builder()
// .beforeUid()
// .build();
//query = mamManager.queryArchive()
//}
messageStore.onMamResult(entity.getAccountId(), entity.getJid(), query);
}));
MercuryConnection connection = connectionMap.get(chat.getPeer().getAccount().getId());
if (connection == null) return;
MamManager mamManager = MamManager.getInstanceFor(connection.getConnection());
MamManager.MamQuery query;
//if (chat.getEarliestMamMessageId() == null) {
try {
query = mamManager.queryMostRecentPage(chat.getPeer().getJid(), 100);
messageStore.handleMamResult(chat.getPeer().getAccount().getId(), chat.getPeer().getJid(), query);
} catch (SmackException.NoResponseException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | SmackException.NotLoggedInException | InterruptedException e) {
e.printStackTrace();
}
//} else {
//MamManager.MamQueryArgs queryArgs = MamManager.MamQueryArgs.builder()
// .beforeUid()
// .build();
//query = mamManager.queryArchive()
//}
}
}

View File

@ -2,12 +2,13 @@ package org.mercury_im.messenger.core.di;
import org.mercury_im.messenger.core.NotificationManager;
import org.mercury_im.messenger.core.centers.ConnectionCenter;
import org.mercury_im.messenger.core.stores.EntityCapsStore;
import org.mercury_im.messenger.core.stores.PlainMessageStore;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;
import org.mercury_im.messenger.persistence.repository.MessageRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;
import org.mercury_im.messenger.core.stores.EntityCapsStore;
import org.mercury_im.messenger.core.stores.PlainMessageStore;
import javax.inject.Singleton;
@ -19,20 +20,26 @@ public class CenterModule {
@Singleton
@Provides
static ConnectionCenter provideConnectionCenter(EntityCapsStore capsStore, PlainMessageStore messageStore, AccountRepository accountRepository, RosterRepository rosterRepository) {
static ConnectionCenter provideConnectionCenter(EntityCapsStore capsStore,
PlainMessageStore messageStore,
AccountRepository accountRepository,
RosterRepository rosterRepository) {
return new ConnectionCenter(capsStore, messageStore, accountRepository, rosterRepository);
}
@Singleton
@Provides
static EntityCapsStore providerEntityCapsStore(EntityCapsRepository capsRepository) {
return new EntityCapsStore(capsRepository);
static EntityCapsStore providerEntityCapsStore(EntityCapsRepository entityCapsRepository) {
return new EntityCapsStore(entityCapsRepository);
}
@Singleton
@Provides
static PlainMessageStore provideMessageStore(MessageRepository messageRepository, NotificationManager notificationManager) {
return new PlainMessageStore(messageRepository, notificationManager);
static PlainMessageStore provideMessageStore(RosterRepository rosterRepository,
ChatRepository chatRepository,
MessageRepository messageRepository,
NotificationManager notificationManager) {
return new PlainMessageStore(rosterRepository, chatRepository, messageRepository, notificationManager);
}
}

View File

@ -1,11 +1,16 @@
package org.mercury_im.messenger.core.di;
import org.mercury_im.messenger.persistence.di.RequeryModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = CenterModule.class)
@Component(modules = {
CenterModule.class,
RequeryModule.class
})
public interface XmppComponent {
}

View File

@ -4,24 +4,21 @@ import org.jivesoftware.smack.util.PacketParserUtils;
import org.jivesoftware.smack.xml.XmlPullParser;
import org.jivesoftware.smackx.caps.cache.EntityCapsPersistentCache;
import org.jivesoftware.smackx.disco.packet.DiscoverInfo;
import org.mercury_im.messenger.persistence.model.EntityCapsModel;
import org.mercury_im.messenger.persistence.entity.EntityCapsModel;
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;
import java.io.StringReader;
import java.util.HashMap;
import java.util.List;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.inject.Inject;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.observers.DisposableSingleObserver;
import io.reactivex.schedulers.Schedulers;
public class EntityCapsStore implements EntityCapsPersistentCache {
@ -31,56 +28,75 @@ public class EntityCapsStore implements EntityCapsPersistentCache {
private final Map<String, DiscoverInfo> discoverInfoMap = new HashMap<>();
private final CompositeDisposable disposable = new CompositeDisposable();
private Observable<List<EntityCapsModel>> allEntityCaps;
@Inject
public EntityCapsStore(EntityCapsRepository repository) {
this.entityCapsRepository = repository;
public EntityCapsStore(EntityCapsRepository entityCapsRepository) {
this.entityCapsRepository = entityCapsRepository;
populateFromDatabase();
}
/*
* Since nodeVers are - if ever - only deleted all at once but added one by one and never
* modified, we can simply determine the set of newly added nodeVers, process those and add
* them to the database.
*/
private void populateFromDatabase() {
allEntityCaps = entityCapsRepository.getAllEntityCaps();
disposable.add(allEntityCaps.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.subscribe(entityCapsModels -> {
discoverInfoMap.clear();
for (EntityCapsModel c : entityCapsModels) {
DiscoverInfo info;
try {
XmlPullParser parser = PacketParserUtils.getParserFor(new StringReader(c.getXml()));
info = (DiscoverInfo) PacketParserUtils.parseIQ(parser);
discoverInfoMap.put(c.getNodeVer(), info);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error parsing EntityCaps: ", e);
}
}
}, throwable -> LOGGER.log(Level.SEVERE, "Error accessing database", throwable)));
disposable.add(entityCapsRepository.getAll()
.subscribe(
entityCapsModels -> {
Map<String, EntityCapsModel> nextEntityCaps = entityCapsModels.toMap(EntityCapsModel.NODE_VER);
// New set of nodeVers
Set<String> nextKeys = nextEntityCaps.keySet();
// Old set of nodeVers
Set<String> previousKeys = discoverInfoMap.keySet();
// Added nodeVers
nextKeys.removeAll(previousKeys);
for (String key : nextKeys) {
// Only add new items. Items itself cannot change, so we don't have to deal
// with changed items.
EntityCapsModel addedModel = nextEntityCaps.get(key);
DiscoverInfo info;
try {
XmlPullParser parser = PacketParserUtils.getParserFor(new StringReader(addedModel.getXml()));
info = (DiscoverInfo) PacketParserUtils.parseIQ(parser);
discoverInfoMap.put(addedModel.getNodeVer(), info);
} catch (Exception e) {
LOGGER.log(Level.SEVERE, "Error parsing EntityCaps: ", e);
}
}
},
error -> LOGGER.log(Level.WARNING, "An error occurred while updating the EntityCaps cache.", error)));
}
@Override
public void addDiscoverInfoByNodePersistent(String nodeVer, DiscoverInfo info) {
EntityCapsModel model = entityCapsRepository.newEntityCapsModel(nodeVer);
EntityCapsModel model = new EntityCapsModel();
model.setNodeVer(nodeVer);
CharSequence xml = info.toXML();
String string = xml.toString();
model.setXml(string);
disposable.add(entityCapsRepository.insertOrReplaceEntityCaps(model)
.subscribeOn(Schedulers.io())
.subscribe());
disposable.add(entityCapsRepository.upsert(model).subscribe(
success -> LOGGER.log(Level.FINE, "Upserted EntityCaps model " + success),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting EntityCaps model", error)
));
}
@Override
public DiscoverInfo lookup(String nodeVer) {
LOGGER.log(Level.INFO, "Looking up caps for " + nodeVer + " in cache...");
LOGGER.log(Level.FINE, "Looking up caps for " + nodeVer + " in cache...");
DiscoverInfo info = discoverInfoMap.get(nodeVer);
LOGGER.log(Level.INFO, "Entry found: " + (info != null ? info.toXML().toString() : "null"));
LOGGER.log(Level.FINE, "Entry found: " + (info != null ? info.toXML().toString() : "null"));
return info;
}
@Override
public void emptyCache() {
entityCapsRepository.deleteAllEntityCaps()
.subscribeOn(Schedulers.io())
.subscribe();
disposable.add(entityCapsRepository.deleteAll().subscribe(
success -> LOGGER.log(Level.FINE, "EntityCaps table cleared successfully."),
error -> LOGGER.log(Level.WARNING, "An error occurred while clearing EntityCaps table.", error)
));
}
}

View File

@ -5,13 +5,24 @@ import org.jivesoftware.smack.chat2.ChatManager;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smackx.carbons.CarbonManager;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
import org.jivesoftware.smackx.delay.DelayInformationManager;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
import org.jivesoftware.smackx.mam.MamManager;
import org.jivesoftware.smackx.sid.element.OriginIdElement;
import org.jivesoftware.smackx.sid.element.StanzaIdElement;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.mercury_im.messenger.core.NotificationManager;
import org.mercury_im.messenger.core.connection.MercuryConnection;
import org.mercury_im.messenger.persistence.model.MessageModel;
import org.mercury_im.messenger.persistence.entity.ChatModel;
import org.mercury_im.messenger.persistence.entity.ContactModel;
import org.mercury_im.messenger.persistence.entity.EntityModel;
import org.mercury_im.messenger.persistence.entity.LastChatMessageRelation;
import org.mercury_im.messenger.persistence.entity.MessageModel;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.persistence.repository.MessageRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.util.ChatAndPossiblyContact;
import java.util.ArrayList;
import java.util.Date;
@ -19,8 +30,8 @@ import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import io.reactivex.Completable;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.Schedulers;
public class PlainMessageStore {
@ -28,75 +39,95 @@ public class PlainMessageStore {
private static final Logger LOGGER = Logger.getLogger(PlainMessageStore.class.getName());
private static final CompositeDisposable disposable = new CompositeDisposable();
private final RosterRepository rosterRepository;
private final ChatRepository chatRepository;
private final MessageRepository messageRepository;
private final NotificationManager notificationManager;
public PlainMessageStore(MessageRepository messageRepository, NotificationManager notificationManager) {
public PlainMessageStore(RosterRepository rosterRepository, ChatRepository chatRepository, MessageRepository messageRepository, NotificationManager notificationManager) {
this.rosterRepository = rosterRepository;
this.chatRepository = chatRepository;
this.messageRepository = messageRepository;
this.notificationManager = notificationManager;
}
public void newIncomingMessage(long accountId, EntityBareJid from, Message message, Chat chat) {
public void handleIncomingMessage(long accountId, EntityBareJid from, Message message, Chat chat) {
if (message.getBody() == null) {
return;
}
org.mercury_im.messenger.persistence.pojo.Chat chatPojo = new org.mercury_im.messenger.persistence.pojo.Chat();
chatPojo.jid = from;
chatPojo.accountId = accountId;
chatPojo.contactName = null;
notificationManager.chatMessageReceived(chatPojo, null, message.getBody());
Completable.fromAction(() -> {
EntityModel entityModel = rosterRepository.getOrCreateEntity(accountId, from)
.blockingGet();
ContactModel contactModel = rosterRepository.getContact(accountId, entityModel.getJid()).blockingFirst().firstOrNull();
ChatModel chatModel = chatRepository.getChatWith(entityModel).blockingFirst().firstOr(() -> {
ChatModel freshChatModel = new ChatModel();
freshChatModel.setPeer(entityModel);
freshChatModel.setDisplayed(true);
return freshChatModel;
});
MessageModel messageModel = messageRepository.newMessageModel();
messageModel.setAccountId(accountId);
messageModel.setFrom(chat.getXmppAddressOfChatPartner());
messageModel.setTo(message.getTo().asEntityBareJidIfPossible());
messageModel.setIncoming(true);
messageModel.setBody(message.getBody());
messageModel.setSendDate(new Date());
chatModel = chatRepository.upsert(chatModel).blockingGet();
disposable.add(
messageRepository.insertMessage(messageModel)
.subscribeOn(Schedulers.io())
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted incoming message " + messageId)));
MessageModel messageModel = setCommonMessageAttributes(message, chatModel);
messageModel.setSender(from);
messageModel.setIncoming(true);
final ChatModel fChatModel = chatModel;
disposable.add(messageRepository.insert(messageModel)
.subscribe(insertedMessageModel -> {
if (message.getBody() != null) {
notificationManager.chatMessageReceived(new ChatAndPossiblyContact(fChatModel, contactModel), message.getBody());
}
LastChatMessageRelation lastMessage = new LastChatMessageRelation();
lastMessage.setChat(fChatModel);
lastMessage.setMessage(insertedMessageModel);
}));
}).subscribeOn(Schedulers.io())
.subscribe();
}
public void newOutgoingMessage(long accountId, EntityBareJid to, Message message, Chat chat) {
MessageModel messageModel = messageRepository.newMessageModel();
messageModel.setAccountId(accountId);
messageModel.setFrom(message.getFrom() != null ? message.getFrom().asEntityBareJidIfPossible() : null);
messageModel.setTo(chat.getXmppAddressOfChatPartner());
messageModel.setIncoming(false);
messageModel.setBody(message.getBody());
messageModel.setSendDate(new Date());
public void handleOutgoingMessage(long accountId, EntityBareJid to, Message message, Chat chat) {
MessageModel model = setCommonMessageAttributes(message, null);
EntityModel entityModel = rosterRepository.getOrCreateEntity(accountId, to).blockingGet();
disposable.add(
messageRepository.insertMessage(messageModel)
.subscribeOn(Schedulers.io())
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted outgoing message " + messageId)));
model.setIncoming(false);
model.setTimestamp(new Date());
model.setSender(entityModel.getAccount().getJid());
model.setRecipient(to);
ChatModel chatModel = chatRepository.getChatWith(entityModel).blockingFirst().firstOr(() -> {
ChatModel freshChatModel = new ChatModel();
freshChatModel.setPeer(entityModel);
freshChatModel.setDisplayed(true);
return freshChatModel;
});
model.setChat(chatModel);
disposable.add(messageRepository.upsert(model)
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted outgoing message " + messageId)));
}
public void onCarbonCopyReceived(long accountId, CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage) {
public void handleCarbonCopy(long accountId, CarbonExtension.Direction direction, Message carbonCopy, Message wrappingMessage) {
if (carbonCopy.getBody() == null) {
return;
}
MessageModel messageModel = messageRepository.newMessageModel();
messageModel.setAccountId(accountId);
messageModel.setFrom(carbonCopy.getFrom() != null ? carbonCopy.getFrom().asEntityBareJidIfPossible() : null);
messageModel.setTo(carbonCopy.getTo() != null ? carbonCopy.getTo().asEntityBareJidIfPossible() : null);
MessageModel messageModel = new MessageModel();
messageModel.setSender(carbonCopy.getFrom() != null ? carbonCopy.getFrom().asEntityBareJidIfPossible() : null);
messageModel.setRecipient(carbonCopy.getTo() != null ? carbonCopy.getTo().asEntityBareJidIfPossible() : null);
messageModel.setIncoming(direction == CarbonExtension.Direction.received);
messageModel.setBody(carbonCopy.getBody());
messageModel.setSendDate(new Date());
disposable.add(
messageRepository.insertMessage(messageModel)
.subscribeOn(Schedulers.io())
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted carbon message " + messageId)));
messageModel.setTimestamp(new Date());
disposable.add(messageRepository.upsert(messageModel)
.subscribe(messageId ->
LOGGER.log(Level.INFO, "Inserted carbon message " + messageId)));
}
public void registerForMercuryConnection(MercuryConnection connection) {
@ -105,13 +136,15 @@ public class PlainMessageStore {
// Add account ID to
chatManager.addIncomingListener((from, message, chat) ->
PlainMessageStore.this.newIncomingMessage(
PlainMessageStore.this.handleIncomingMessage(
connection.getAccountId(), from, message, chat));
chatManager.addOutgoingListener((to, message, chat) ->
PlainMessageStore.this.newOutgoingMessage(
PlainMessageStore.this.handleOutgoingMessage(
connection.getAccountId(), to, message, chat));
carbonManager.addCarbonCopyReceivedListener((direction, carbonCopy, wrappingMessage) ->
PlainMessageStore.this.onCarbonCopyReceived(
PlainMessageStore.this.handleCarbonCopy(
connection.getAccountId(), direction, carbonCopy, wrappingMessage));
}
@ -119,7 +152,7 @@ public class PlainMessageStore {
disposable.clear();
}
public void onMamResult(long accountId, EntityBareJid peerJid, MamManager.MamQuery query) {
public void handleMamResult(long accountId, EntityBareJid peerJid, MamManager.MamQuery query) {
List<MessageModel> messageModels = new ArrayList<>();
for (Message message : query.getMessages()) {
Date date = new Date();
@ -128,19 +161,42 @@ public class PlainMessageStore {
date = delay.getStamp();
}
MessageModel messageModel = messageRepository.newMessageModel();
messageModel.setAccountId(accountId);
MessageModel messageModel = new MessageModel();
messageModel.setBody(message.getBody());
messageModel.setFrom(message.getFrom().asEntityBareJidOrThrow());
messageModel.setTo(message.getTo().asEntityBareJidOrThrow());
messageModel.setSender(message.getFrom().asEntityBareJidOrThrow());
messageModel.setRecipient(message.getTo().asEntityBareJidOrThrow());
messageModel.setIncoming(peerJid.equals(message.getFrom().asEntityBareJidOrThrow()));
messageModel.setSendDate(date);
messageModel.setTimestamp(date);
messageModels.add(messageModel);
}
disposable.add(
messageRepository.insertMessages(messageModels)
.subscribeOn(Schedulers.io())
.subscribe());
disposable.add(messageRepository.upsert(messageModels).subscribe());
}
private MessageModel incomingMessageToModel(Message message, ChatModel chat) {
MessageModel model = setCommonMessageAttributes(message, chat);
model.setIncoming(true);
return model;
}
private MessageModel setCommonMessageAttributes(Message message, ChatModel chat) {
MessageModel model = new MessageModel();
model.setBody(message.getBody());
Date timestamp = DelayInformationManager.getDelayTimestamp(message);
model.setTimestamp(timestamp == null ? new Date() : timestamp);
model.setThread(message.getThread());
model.setLegacyId(message.getStanzaId());
model.setChat(chat);
model.setRecipient(message.getTo().asEntityBareJidOrThrow());
model.setSender(message.getFrom() != null ? message.getFrom().asEntityBareJidIfPossible() : null);
OriginIdElement originId = OriginIdElement.getOriginId(message);
model.setOriginId(originId != null ? originId.getId() : null);
StanzaIdElement stanzaId = StanzaIdElement.getStanzaId(message);
model.setStanzaId(stanzaId != null ? stanzaId.getId() : null);
model.setStanzaIdBy(stanzaId != null ? JidCreate.entityBareFromOrThrowUnchecked(stanzaId.getBy()) : null);
return model;
}
}

View File

@ -2,10 +2,12 @@ package org.mercury_im.messenger.core.stores;
import org.jivesoftware.smack.roster.packet.RosterPacket;
import org.jxmpp.jid.Jid;
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 org.mercury_im.messenger.persistence.entity.AccountModel;
import org.mercury_im.messenger.persistence.entity.ContactModel;
import org.mercury_im.messenger.persistence.entity.EntityModel;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.enums.SubscriptionDirection;
import java.util.ArrayList;
import java.util.Arrays;
@ -18,8 +20,9 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import io.reactivex.Scheduler;
import io.reactivex.disposables.CompositeDisposable;
import io.reactivex.functions.Consumer;
import io.reactivex.functions.Action;
import io.reactivex.schedulers.Schedulers;
public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.RosterStore {
@ -27,16 +30,17 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
private static final Logger LOGGER = Logger.getLogger(RosterStore.class.getName());
private final RosterRepository rosterRepository;
private long accountId;
private final AccountRepository accountRepository;
private AccountModel account;
private CompositeDisposable disposable = null;
private final Map<Jid, RosterPacket.Item> itemMap = new HashMap<>();
private String rosterVersion;
@Inject
public RosterStore(RosterRepository rosterRepository) {
public RosterStore(RosterRepository rosterRepository, AccountRepository accountRepository) {
this.rosterRepository = rosterRepository;
this.accountRepository = accountRepository;
}
public void subscribe() {
@ -46,30 +50,23 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
}
disposable = new CompositeDisposable();
disposable.add(rosterRepository.getAllContactsOfAccount(accountId)
.subscribeOn(Schedulers.io())
disposable.add(rosterRepository.getAllContactsOfAccount(account)
.observeOn(Schedulers.computation())
.subscribe((Consumer<List<? extends ContactModel>>) contactsList -> {
itemMap.clear();
for (ContactModel contactModel : contactsList) {
rosterRepository.getEntityForContact(contactModel.getId())
.subscribeOn(Schedulers.io())
.subscribe((Consumer<EntityModel>) entityModel -> {
RosterPacket.Item item = fromModel(contactModel);
itemMap.put(entityModel.getJid(), item);
});
LOGGER.log(Level.INFO, "Populate itemMap with " + contactsList.size() + " items");
.subscribe(contactsList -> {
itemMap.clear();
for (ContactModel contactModel : contactsList) {
itemMap.put(contactModel.getEntity().getJid(), fromModel(contactModel));
LOGGER.log(Level.INFO, "Populate itemMap with " + contactsList.toList().size() + " items");
}
}));
}
},
error -> LOGGER.log(Level.WARNING, "An error occurred while updating roster cache", error)));
disposable.add(rosterRepository.getRosterInformationForAccount(accountId)
.subscribeOn(Schedulers.io())
disposable.add(rosterRepository.getRosterVersion(account)
.observeOn(Schedulers.computation())
.subscribe((Consumer<RosterInformationModel>) s -> {
LOGGER.log(Level.INFO, "Set rosterVer = " + s.getRosterVersion());
rosterVersion = s.getRosterVersion();
}));
.subscribe(
result -> setRosterVersion(result),
error -> LOGGER.log(Level.WARNING, "An error occurred updating cached roster version", error)));
}
public void unsubscribe() {
@ -81,7 +78,13 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
}
public void setAccountId(long accountId) {
this.accountId = accountId;
this.account = accountRepository.getAccount(accountId)
.doOnSubscribe(subscribe -> LOGGER.log(Level.FINE, "Fetching account " + accountId))
.blockingFirst().first();
}
private void setRosterVersion(String rosterVersion) {
this.rosterVersion = rosterVersion;
}
@Override
@ -104,14 +107,16 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
LOGGER.log(Level.INFO, "Add entry " + item.toXML().toString());
// Update database
ContactModel contact = toModel(item);
rosterRepository.upsertContact(contact)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
rosterRepository.updateRosterVersionForAccount(accountId, version)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
disposable.add(rosterRepository.upsertContact(contact)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted contact model " + success + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting contact " + contact, error)
));
disposable.add(rosterRepository.updateRosterVersion(account, version)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted roster version to " + rosterVersion + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting roster version", error)
));
return true;
}
@ -120,15 +125,21 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
public boolean resetEntries(Collection<RosterPacket.Item> items, String version) {
LOGGER.log(Level.INFO, "Reset Entries: " + Arrays.toString(items.toArray()));
// Update database
// TODO: Delete other contacts
for (RosterPacket.Item item : items) {
ContactModel model = toModel(item);
rosterRepository.upsertContact(model)
.subscribeOn(Schedulers.io())
.subscribe();
disposable.add(rosterRepository.upsertContact(model)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted contact model " + success + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting contact " + model, error)
));
}
rosterRepository.updateRosterVersionForAccount(accountId, version)
.subscribeOn(Schedulers.io())
.subscribe();
disposable.add(rosterRepository.updateRosterVersion(account, version)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted roster version to " + rosterVersion + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting roster version", error)
));
return true;
}
@ -136,54 +147,60 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
@Override
public boolean removeEntry(Jid bareJid, String version) {
LOGGER.log(Level.INFO, "Remove entry " + bareJid.toString());
rosterRepository.deleteContact(accountId, bareJid.asEntityBareJidOrThrow())
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
rosterRepository.updateRosterVersionForAccount(accountId, version)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
disposable.add(rosterRepository.deleteContact(account.getId(), bareJid.asEntityBareJidOrThrow())
.subscribe(
() -> LOGGER.log(Level.FINE, "Deletion of contact " + bareJid.toString() + " successful"),
error -> LOGGER.log(Level.WARNING, "An error occurred deleting contact " + bareJid.toString(), error)
));
disposable.add(rosterRepository.updateRosterVersion(account, version)
.subscribe(
success -> LOGGER.log(Level.FINE, "Upserted roster version to " + rosterVersion + " successfully"),
error -> LOGGER.log(Level.WARNING, "An error occurred upserting roster version", error)
));
return true;
}
@Override
public void resetStore() {
LOGGER.log(Level.INFO, "Reset Store");
rosterRepository.deleteAllContactsOfAccount(accountId)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
rosterRepository.updateRosterVersionForAccount(accountId, "")
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe();
disposable.add(rosterRepository.deleteAllContactsOfAccount(account)
.subscribe(
success -> LOGGER.log(Level.FINE, "Successfully reset store."),
error -> LOGGER.log(Level.WARNING, "An error occurred resetting store", error)
));
disposable.add(rosterRepository.updateRosterVersion(account, "")
.subscribe(
success -> LOGGER.log(Level.FINE, "Successfully reset roster version"),
error -> LOGGER.log(Level.WARNING, "An error occurred resetting roster version", error)
));
}
public RosterPacket.Item fromModel(ContactModel contactModel) {
RosterPacket.Item item = new RosterPacket.Item(
contactModel.getEntity().getJid(),
contactModel.getRosterName());
if (contactModel.getDirection() != null) {
item.setItemType(convert(contactModel.getDirection()));
contactModel.getRostername());
if (contactModel.getSub_direction() != null) {
item.setItemType(convert(contactModel.getSub_direction()));
}
item.setApproved(contactModel.isApproved());
item.setSubscriptionPending(contactModel.isSubscriptionPending());
item.setApproved(contactModel.isSub_approved());
item.setSubscriptionPending(contactModel.isSub_pending());
return item;
}
public ContactModel toModel(RosterPacket.Item item) {
ContactModel contact = rosterRepository.newContactModel();
ContactModel contact = new ContactModel();
contact.setRosterName(item.getName());
contact.setRostername(item.getName());
if (item.getItemType() != null) {
contact.setDirection(convert(item.getItemType()));
contact.setSub_direction(convert(item.getItemType()));
}
contact.setApproved(item.isApproved());
contact.setSubscriptionPending(item.isSubscriptionPending());
contact.setSub_approved(item.isApproved());
contact.setSub_pending(item.isSubscriptionPending());
EntityModel entity = rosterRepository.newEntityModel();
entity.setAccountId(accountId);
EntityModel entity = new EntityModel();
entity.setAccount(account);
entity.setJid(item.getJid().asEntityBareJidOrThrow());
contact.setEntity(entity);
@ -191,11 +208,12 @@ public class RosterStore implements org.jivesoftware.smack.roster.rosterstore.Ro
return contact;
}
public ContactModel.DIRECTION convert(RosterPacket.ItemType type) {
return ContactModel.DIRECTION.valueOf(type.toString());
public SubscriptionDirection convert(RosterPacket.ItemType type) {
return SubscriptionDirection.valueOf(type.toString());
}
public RosterPacket.ItemType convert(ContactModel.DIRECTION direction) {
public RosterPacket.ItemType convert(SubscriptionDirection direction) {
return RosterPacket.ItemType.fromString(direction.toString());
}
}

View File

@ -1,16 +1,28 @@
package org.mercury_im.messenger.core.util;
import org.mercury_im.messenger.persistence.model.ContactModel;
import org.mercury_im.messenger.persistence.entity.ContactModel;
import org.mercury_im.messenger.persistence.entity.EntityModel;
public class ContactNameUtil {
public static String displayableNameFrom(ContactModel contactModel) {
if (contactModel.getRosterName() != null) {
return contactModel.getRosterName();
if (contactModel == null) {
return null;
}
if (contactModel.getRostername() != null) {
return contactModel.getRostername();
}
if (contactModel.getEntity() != null) {
return contactModel.getEntity().getJid().getLocalpart().asUnescapedString();
}
return null;
}
public static String displayableNameFrom(ContactModel contact, EntityModel entity) {
if (contact == null) {
return entity.getJid().getLocalpart().asUnescapedString();
} return displayableNameFrom(contact);
}
}

View File

@ -1,3 +0,0 @@
/build
*.iml
/schemas

View File

@ -1,31 +0,0 @@
# Room Persistence Layer of Mercury
This Android Module contains an implementation of the interfaces of the `persistence` module.
In particular it defines SQL schemes and provides DAOs as well as implementations of Model classes
and Repositories that utilize the [Room Database Framework](https://developer.android.com/topic/libraries/architecture/room).
## Packages and Classes
### `AppDatabase`
defines what makes up the database itself. It lists all available entities and provides dao classes.
### `dao` package
Contains data access objects (DAOs) which provide CRUD methods (Create, Read, Update, Delete) for
all the models.
Note, that the `BaseDao` interface already defines methods for creating, updating and deleting
entities and is extended by most DAO classes.
### `model` package
Contains classes that represent the data itself in form of models.
### `repository` package
Contains implementations of data repositories. Repositories are single sources of truth and provide
a user-friendly separation layer between the application and the DAOs.
Ideally this layer would also provide access to the XMPP domain, but this is still subject to
discussion.
### `type_converter` package
Contains Room specific type converter that convert non-basic data types into basic data types which
can be handled by the database and vice versa.

View File

@ -1,56 +0,0 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 29
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility 1.8
targetCompatibility 1.8
}
}
dependencies {
api project(":persistence")
// Dagger 2 for dependency injection
implementation "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
// Room
api "androidx.room:room-runtime:$roomVersion"
annotationProcessor "androidx.room:room-compiler:$roomVersion"
implementation "androidx.room:room-rxjava2:$roomRxJavaVersion"
api "io.reactivex.rxjava2:rxandroid:$rxAndroidVersion"
// Test
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test:runner:$andxTestRunnerVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$andxTestEspressoVersion"
androidTestImplementation "androidx.test:core:$andxTestCoreVersion"
androidTestImplementation "androidx.test.ext:junit:$andxTestJunitVersion"
}

View File

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -1,518 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "6322b5b46e09460ae6f30134786c9221",
"entities": [
{
"tableName": "accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_account_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `jid` TEXT, `password` TEXT, `enabled` INTEGER NOT NULL, `state` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "jid",
"columnName": "jid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "state",
"columnName": "state",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_account_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_accounts_pk_account_id",
"unique": false,
"columnNames": [
"pk_account_id"
],
"createSql": "CREATE INDEX `index_accounts_pk_account_id` ON `${TABLE_NAME}` (`pk_account_id`)"
}
],
"foreignKeys": []
},
{
"tableName": "entities",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_entity_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `jid` TEXT NOT NULL, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "fk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "jid",
"columnName": "jid",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"pk_entity_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_entities_pk_entity_id",
"unique": false,
"columnNames": [
"pk_entity_id"
],
"createSql": "CREATE INDEX `index_entities_pk_entity_id` ON `${TABLE_NAME}` (`pk_entity_id`)"
},
{
"name": "index_entities_fk_account_id_jid",
"unique": true,
"columnNames": [
"fk_account_id",
"jid"
],
"createSql": "CREATE UNIQUE INDEX `index_entities_fk_account_id_jid` ON `${TABLE_NAME}` (`fk_account_id`, `jid`)"
}
],
"foreignKeys": [
{
"table": "accounts",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"fk_account_id"
],
"referencedColumns": [
"pk_account_id"
]
}
]
},
{
"tableName": "contacts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_contact_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_entity_id` INTEGER NOT NULL, `rostername` TEXT, `sub_direction` TEXT, `sub_pending` INTEGER NOT NULL, `sub_approved` INTEGER NOT NULL, FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_contact_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "entityId",
"columnName": "fk_entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rosterName",
"columnName": "rostername",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "direction",
"columnName": "sub_direction",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "subscriptionPending",
"columnName": "sub_pending",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "approved",
"columnName": "sub_approved",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"pk_contact_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_contacts_pk_contact_id",
"unique": false,
"columnNames": [
"pk_contact_id"
],
"createSql": "CREATE INDEX `index_contacts_pk_contact_id` ON `${TABLE_NAME}` (`pk_contact_id`)"
},
{
"name": "index_contacts_fk_entity_id",
"unique": true,
"columnNames": [
"fk_entity_id"
],
"createSql": "CREATE UNIQUE INDEX `index_contacts_fk_entity_id` ON `${TABLE_NAME}` (`fk_entity_id`)"
},
{
"name": "index_contacts_pk_contact_id_fk_entity_id",
"unique": true,
"columnNames": [
"pk_contact_id",
"fk_entity_id"
],
"createSql": "CREATE UNIQUE INDEX `index_contacts_pk_contact_id_fk_entity_id` ON `${TABLE_NAME}` (`pk_contact_id`, `fk_entity_id`)"
}
],
"foreignKeys": [
{
"table": "entities",
"onDelete": "RESTRICT",
"onUpdate": "NO ACTION",
"columns": [
"fk_entity_id"
],
"referencedColumns": [
"pk_entity_id"
]
}
]
},
{
"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": []
},
{
"tableName": "chats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_chat_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_entity_id` INTEGER NOT NULL, `active` INTEGER NOT NULL, `last_read_message` INTEGER NOT NULL, `most_recent_mam_msg` TEXT, `earliest_mam_msg` TEXT, FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_chat_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "peerEntityId",
"columnName": "fk_entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isActive",
"columnName": "active",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastReadMessageId",
"columnName": "last_read_message",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mostRecentMamMessageId",
"columnName": "most_recent_mam_msg",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "earliestMamMessageId",
"columnName": "earliest_mam_msg",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_chat_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_chats_pk_chat_id",
"unique": false,
"columnNames": [
"pk_chat_id"
],
"createSql": "CREATE INDEX `index_chats_pk_chat_id` ON `${TABLE_NAME}` (`pk_chat_id`)"
},
{
"name": "index_chats_fk_entity_id",
"unique": false,
"columnNames": [
"fk_entity_id"
],
"createSql": "CREATE INDEX `index_chats_fk_entity_id` ON `${TABLE_NAME}` (`fk_entity_id`)"
}
],
"foreignKeys": [
{
"table": "entities",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"fk_entity_id"
],
"referencedColumns": [
"pk_entity_id"
]
}
]
},
{
"tableName": "messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_message_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `body` TEXT, `send_date` INTEGER, `from` TEXT, `to` TEXT, `incoming` INTEGER NOT NULL, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_message_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "fk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "body",
"columnName": "body",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sendDate",
"columnName": "send_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "from",
"columnName": "from",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "to",
"columnName": "to",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "incoming",
"columnName": "incoming",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"pk_message_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_messages_pk_message_id",
"unique": false,
"columnNames": [
"pk_message_id"
],
"createSql": "CREATE INDEX `index_messages_pk_message_id` ON `${TABLE_NAME}` (`pk_message_id`)"
},
{
"name": "index_messages_fk_account_id",
"unique": false,
"columnNames": [
"fk_account_id"
],
"createSql": "CREATE INDEX `index_messages_fk_account_id` ON `${TABLE_NAME}` (`fk_account_id`)"
}
],
"foreignKeys": [
{
"table": "accounts",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"fk_account_id"
],
"referencedColumns": [
"pk_account_id"
]
}
]
},
{
"tableName": "avatars",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_avatar_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_entity_id` INTEGER NOT NULL, `sha1sum` TEXT, `bytes` BLOB, FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE RESTRICT )",
"fields": [
{
"fieldPath": "avatarId",
"columnName": "pk_avatar_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "entityId",
"columnName": "fk_entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "sha1Sum",
"columnName": "sha1sum",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "bytes",
"columnName": "bytes",
"affinity": "BLOB",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_avatar_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_avatars_pk_avatar_id",
"unique": false,
"columnNames": [
"pk_avatar_id"
],
"createSql": "CREATE INDEX `index_avatars_pk_avatar_id` ON `${TABLE_NAME}` (`pk_avatar_id`)"
},
{
"name": "index_avatars_fk_entity_id",
"unique": true,
"columnNames": [
"fk_entity_id"
],
"createSql": "CREATE UNIQUE INDEX `index_avatars_fk_entity_id` ON `${TABLE_NAME}` (`fk_entity_id`)"
}
],
"foreignKeys": [
{
"table": "entities",
"onDelete": "RESTRICT",
"onUpdate": "NO ACTION",
"columns": [
"fk_entity_id"
],
"referencedColumns": [
"pk_entity_id"
]
}
]
},
{
"tableName": "entity_caps",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_node_ver` TEXT NOT NULL, `xml` TEXT, PRIMARY KEY(`pk_node_ver`))",
"fields": [
{
"fieldPath": "nodeVer",
"columnName": "pk_node_ver",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "xml",
"columnName": "xml",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_node_ver"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_entity_caps_pk_node_ver",
"unique": false,
"columnNames": [
"pk_node_ver"
],
"createSql": "CREATE INDEX `index_entity_caps_pk_node_ver` ON `${TABLE_NAME}` (`pk_node_ver`)"
}
],
"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, '6322b5b46e09460ae6f30134786c9221')"
]
}
}

View File

@ -1,382 +0,0 @@
{
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "1149b7d295726ec2043486e660dcf75d",
"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 )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_contact_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "fk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "entityId",
"columnName": "fk_entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "rosterName",
"columnName": "rostername",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "nickname",
"columnName": "nickname",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_contact_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_contacts_pk_contact_id",
"unique": false,
"columnNames": [
"pk_contact_id"
],
"createSql": "CREATE INDEX `index_contacts_pk_contact_id` ON `${TABLE_NAME}` (`pk_contact_id`)"
},
{
"name": "index_contacts_fk_account_id",
"unique": false,
"columnNames": [
"fk_account_id"
],
"createSql": "CREATE INDEX `index_contacts_fk_account_id` ON `${TABLE_NAME}` (`fk_account_id`)"
},
{
"name": "index_contacts_fk_entity_id",
"unique": false,
"columnNames": [
"fk_entity_id"
],
"createSql": "CREATE INDEX `index_contacts_fk_entity_id` ON `${TABLE_NAME}` (`fk_entity_id`)"
},
{
"name": "index_contacts_pk_contact_id_fk_entity_id",
"unique": true,
"columnNames": [
"pk_contact_id",
"fk_entity_id"
],
"createSql": "CREATE UNIQUE INDEX `index_contacts_pk_contact_id_fk_entity_id` ON `${TABLE_NAME}` (`pk_contact_id`, `fk_entity_id`)"
}
],
"foreignKeys": [
{
"table": "accounts",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"fk_account_id"
],
"referencedColumns": [
"pk_account_id"
]
},
{
"table": "entities",
"onDelete": "RESTRICT",
"onUpdate": "NO ACTION",
"columns": [
"fk_entity_id"
],
"referencedColumns": [
"pk_entity_id"
]
}
]
},
{
"tableName": "accounts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_account_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `jid` TEXT, `password` TEXT, `enabled` INTEGER NOT NULL, `state` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "jid",
"columnName": "jid",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "enabled",
"columnName": "enabled",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "state",
"columnName": "state",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_account_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_accounts_pk_account_id",
"unique": false,
"columnNames": [
"pk_account_id"
],
"createSql": "CREATE INDEX `index_accounts_pk_account_id` ON `${TABLE_NAME}` (`pk_account_id`)"
}
],
"foreignKeys": []
},
{
"tableName": "chats",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_chat_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_entity_id` INTEGER NOT NULL, `active` INTEGER NOT NULL, FOREIGN KEY(`fk_entity_id`) REFERENCES `entities`(`pk_entity_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_chat_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "peerEntityId",
"columnName": "fk_entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isActive",
"columnName": "active",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"pk_chat_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_chats_pk_chat_id",
"unique": false,
"columnNames": [
"pk_chat_id"
],
"createSql": "CREATE INDEX `index_chats_pk_chat_id` ON `${TABLE_NAME}` (`pk_chat_id`)"
},
{
"name": "index_chats_fk_entity_id",
"unique": false,
"columnNames": [
"fk_entity_id"
],
"createSql": "CREATE INDEX `index_chats_fk_entity_id` ON `${TABLE_NAME}` (`fk_entity_id`)"
}
],
"foreignKeys": [
{
"table": "entities",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"fk_entity_id"
],
"referencedColumns": [
"pk_entity_id"
]
}
]
},
{
"tableName": "messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_message_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `body` TEXT, `send_date` INTEGER, `from` TEXT, `to` TEXT, `incoming` INTEGER NOT NULL, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_message_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "fk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "body",
"columnName": "body",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "sendDate",
"columnName": "send_date",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "from",
"columnName": "from",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "to",
"columnName": "to",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "incoming",
"columnName": "incoming",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"pk_message_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_messages_pk_message_id",
"unique": false,
"columnNames": [
"pk_message_id"
],
"createSql": "CREATE INDEX `index_messages_pk_message_id` ON `${TABLE_NAME}` (`pk_message_id`)"
},
{
"name": "index_messages_fk_account_id",
"unique": false,
"columnNames": [
"fk_account_id"
],
"createSql": "CREATE INDEX `index_messages_fk_account_id` ON `${TABLE_NAME}` (`fk_account_id`)"
}
],
"foreignKeys": [
{
"table": "accounts",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"fk_account_id"
],
"referencedColumns": [
"pk_account_id"
]
}
]
},
{
"tableName": "entities",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pk_entity_id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `fk_account_id` INTEGER NOT NULL, `jid` TEXT NOT NULL, `avatar` TEXT, FOREIGN KEY(`fk_account_id`) REFERENCES `accounts`(`pk_account_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
"columnName": "pk_entity_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "accountId",
"columnName": "fk_account_id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "jid",
"columnName": "jid",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "avatarFile",
"columnName": "avatar",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"pk_entity_id"
],
"autoGenerate": true
},
"indices": [
{
"name": "index_entities_pk_entity_id",
"unique": false,
"columnNames": [
"pk_entity_id"
],
"createSql": "CREATE INDEX `index_entities_pk_entity_id` ON `${TABLE_NAME}` (`pk_entity_id`)"
},
{
"name": "index_entities_fk_account_id_jid",
"unique": true,
"columnNames": [
"fk_account_id",
"jid"
],
"createSql": "CREATE UNIQUE INDEX `index_entities_fk_account_id_jid` ON `${TABLE_NAME}` (`fk_account_id`, `jid`)"
}
],
"foreignKeys": [
{
"table": "accounts",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"fk_account_id"
],
"referencedColumns": [
"pk_account_id"
]
}
]
}
],
"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, '1149b7d295726ec2043486e660dcf75d')"
]
}
}

View File

@ -1,51 +0,0 @@
package org.mercury_im.messenger.persistence.room;
import android.content.Context;
import androidx.room.Room;
import androidx.test.core.app.ApplicationProvider;
import org.junit.After;
import org.junit.Before;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
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.IEntityCapsRepository;
import org.mercury_im.messenger.persistence.room.repository.IMessageRepository;
import org.mercury_im.messenger.persistence.room.repository.IRosterRepository;
public abstract class AbstractDatabaseTest {
protected AppDatabase db;
protected IAccountRepository accountRepository;
protected IRosterRepository rosterRepository;
protected IMessageRepository messageRepository;
protected IChatRepository chatRepository;
protected IEntityCapsRepository capsRepository;
protected IAvatarRepository avatarRepository;
protected final EntityBareJid TEST_JID_JULIET = JidCreate.entityBareFromOrThrowUnchecked("juliet@capulet.lit");
protected final EntityBareJid TEST_JID_ROMEO = JidCreate.entityBareFromOrThrowUnchecked("romeo@montague.lit");
protected final EntityBareJid TEST_JID_MERCUTIO = JidCreate.entityBareFromOrThrowUnchecked("mercutio@montague.lit");
@Before
public void createDb() {
Context context = ApplicationProvider.getApplicationContext();
db = Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build();
accountRepository = new IAccountRepository(db.accountDao());
rosterRepository = new IRosterRepository(
db.entityDao(), db.contactDao(), db.rosterInformationDao());
messageRepository = new IMessageRepository(db.messageDao());
chatRepository = new IChatRepository(db.chatDao(), rosterRepository);
capsRepository = new IEntityCapsRepository(db.entityCapsDao());
avatarRepository = new IAvatarRepository(db.avatarDao());
}
@After
public void closeDb() {
db.close();
}
}

View File

@ -1,111 +0,0 @@
package org.mercury_im.messenger.persistence.room;
import android.util.Log;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
import org.mercury_im.messenger.persistence.room.model.RoomChatModel;
import org.mercury_im.messenger.persistence.room.model.RoomContactModel;
import org.mercury_im.messenger.persistence.room.model.RoomEntityModel;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest extends AbstractDatabaseTest {
@Test
public void testUpsertContact1() {
RoomAccountModel accountModel = new RoomAccountModel();
accountModel.setJid(TEST_JID_JULIET);
accountRepository.insertAccount(accountModel).test().assertValue(1L).dispose();
RoomContactModel contactModel = new RoomContactModel();
RoomEntityModel entityModel = new RoomEntityModel();
entityModel.setAccountId(1);
entityModel.setJid(TEST_JID_ROMEO);
contactModel.setEntity(entityModel);
rosterRepository.upsertContact(contactModel).test().assertValue(1L).dispose();
rosterRepository.maybeGetContact(1L).subscribe(contact -> {
System.out.println(contact.getEntity().getAccountId() + " " + contact.getEntity().getJid().toString());
}).dispose();
rosterRepository.upsertContact(contactModel).test().assertValue(1L).dispose();
rosterRepository.maybeGetContact(1L).subscribe(contact -> {
System.out.println(contact.getEntity().getAccountId() + " " + contact.getEntity().getJid().toString());
}).dispose();
}
@Test
public void testGetOrCreateEntity() {
RoomAccountModel accountModel = new RoomAccountModel();
accountModel.setJid(TEST_JID_JULIET);
accountRepository.insertAccount(accountModel).test().assertValue(1L).dispose();
RoomEntityModel romeo = rosterRepository.getOrCreateEntityForAccountAndJid(accountModel, TEST_JID_ROMEO).blockingGet();
RoomEntityModel mercu = rosterRepository.getOrCreateEntityForAccountAndJid(accountModel, TEST_JID_MERCUTIO).blockingGet();
}
@Test
public void testObservabilityOfChat() throws InterruptedException {
RoomAccountModel accountModel = new RoomAccountModel();
accountModel.setJid(TEST_JID_JULIET);
accountRepository.insertAccount(accountModel).test().assertValue(1L).dispose();
Observable<RoomChatModel> chat = chatRepository.getOrCreateChatWith(accountModel.getId(), TEST_JID_ROMEO);
Disposable disposable = chat.subscribe(chatModel -> Log.d(AppDatabase.TAG, "onNext: " + chatModel));
RoomChatModel chatModel = chatRepository.newChatModel();
chatModel.setId(1L);
chatModel.setPeerEntityId(1L);
chatModel.setActive(true);
chatRepository.updateChat(chatModel).blockingAwait();
chatModel.setActive(false);
chatRepository.updateChat(chatModel).blockingAwait();
chatModel.setActive(true);
chatRepository.updateChat(chatModel).blockingAwait();
Thread.sleep(100);
disposable.dispose();
Thread.sleep(100);
}
@Test
public void testUpsertContact() throws InterruptedException {
RoomAccountModel accountModel = new RoomAccountModel();
accountModel.setJid(TEST_JID_ROMEO);
accountRepository.insertAccount(accountModel).test().assertValue(1L).dispose();
RoomContactModel contact = new RoomContactModel();
contact.setRosterName("A");
RoomEntityModel entity = new RoomEntityModel();
entity.setJid(TEST_JID_JULIET);
entity.setAccountId(1L);
contact.setEntity(entity);
rosterRepository.upsertContact(contact).test().assertValue(1L).dispose();
Thread.sleep(100);
contact.setId(0);
contact.setRosterName("B");
contact.getEntity().setId(0);
contact.setEntityId(0);
rosterRepository.upsertContact(contact).test().assertValue(1L).dispose();
rosterRepository.getAllContacts().subscribe(System.out::println);
Thread.sleep(200);
}
}

View File

@ -1,2 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mercury_im.messenger.persistence.room" />

View File

@ -1,112 +0,0 @@
package org.mercury_im.messenger.persistence.room;
import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
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.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.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 = {
RoomAccountModel.class,
RoomEntityModel.class,
RoomContactModel.class,
RoomRosterInformationModel.class,
RoomChatModel.class,
RoomMessageModel.class,
RoomAvatarModel.class,
RoomEntityCapsModel.class
})
public abstract class AppDatabase extends RoomDatabase {
private static final String DB_NAME = "mercury_db";
private static AppDatabase INSTANCE;
public static final String TAG = "PERSISTENCE_ROOM";
public static synchronized 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();
}
return INSTANCE;
}
/**
* 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

@ -1,86 +0,0 @@
package org.mercury_im.messenger.persistence.room;
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.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;
import dagger.Module;
import dagger.Provides;
/**
* Provides the {@link AppDatabase}, DAOs and Repositories.
*/
@Module(includes = RoomRepositoryModule.class)
public class RoomModule {
private AppDatabase mAppDatabase;
@Inject
public RoomModule(Application application) {
mAppDatabase = AppDatabase.getDatabase(application);
}
@Singleton
@Provides
AppDatabase provideAppDatabase() {
return mAppDatabase;
}
@Singleton
@Provides
AccountDao provideAccountDao() {
return mAppDatabase.accountDao();
}
@Singleton
@Provides
ContactDao provideContactDao() {
return mAppDatabase.contactDao();
}
@Singleton
@Provides
ChatDao provideChatDao() {
return mAppDatabase.chatDao();
}
@Singleton
@Provides
MessageDao provideMessageDao() {
return mAppDatabase.messageDao();
}
@Singleton
@Provides
EntityDao provideEntityDao() {
return mAppDatabase.entityDao();
}
@Singleton
@Provides
AvatarDao providerAvatarDao() {
return mAppDatabase.avatarDao();
}
@Singleton
@Provides
RosterInformationDao provideRosterInformationDao() {
return mAppDatabase.rosterInformationDao();
}
@Singleton
@Provides
EntityCapsDao provideEntityCapsDao() {
return mAppDatabase.entityCapsDao();
}
}

View File

@ -1,67 +0,0 @@
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.EntityCapsRepository;
import org.mercury_im.messenger.persistence.repository.MessageRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
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.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.IEntityCapsRepository;
import org.mercury_im.messenger.persistence.room.repository.IMessageRepository;
import org.mercury_im.messenger.persistence.room.repository.IRosterRepository;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class RoomRepositoryModule {
@Singleton
@Provides
AccountRepository provideAccountRepository(AccountDao dao) {
return new IAccountRepository(dao);
}
@Singleton
@Provides
ChatRepository provideChatRepository(ChatDao chatDao, RosterRepository rosterRepository) {
return new IChatRepository(chatDao, rosterRepository);
}
@Singleton
@Provides
MessageRepository provideMessageRepository(MessageDao dao) {
return new IMessageRepository(dao);
}
@Singleton
@Provides
RosterRepository provideContactRepository(EntityDao entityDao, ContactDao contactDao, RosterInformationDao rosterInformationDao) {
return new IRosterRepository(entityDao, contactDao, rosterInformationDao);
}
@Singleton
@Provides
AvatarRepository provideAvatarRepository(AvatarDao avatarDao) {
return new IAvatarRepository(avatarDao);
}
@Singleton
@Provides
EntityCapsRepository providerEntityCapsRepository(EntityCapsDao entityCapsDao) {
return new IEntityCapsRepository(entityCapsDao);
}
}

View File

@ -1,52 +0,0 @@
package org.mercury_im.messenger.persistence.room.dao;
import androidx.annotation.WorkerThread;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.room.model.RoomAccountModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
@Dao
@TypeConverters(EntityBareJidConverter.class)
@WorkerThread
public interface AccountDao extends BaseDao<RoomAccountModel> {
/**
* Return an {@link Observable} wrapping a {@link List} which contains all
* {@link RoomAccountModel Accounts} which are currently stored in the database.
*
* @return live updating account list
*/
@Query("select * from accounts")
Observable<List<RoomAccountModel>> getAllAccounts();
/**
* Return the {@link RoomAccountModel Account} which is identified by the given id.
*
* @param id id of the account
* @return account or null
*/
@Query("select * from accounts where pk_account_id = :id")
Maybe<RoomAccountModel> maybeGetAccountById(long id);
@Query("select * from accounts where pk_account_id = :id")
Observable<RoomAccountModel> getAccountById(long id);
@Query("select * from accounts where jid = :jid")
Maybe<RoomAccountModel> getAccountByJid(EntityBareJid jid);
@Query("update accounts set state = :state where pk_account_id = :accountId")
Completable updateConnectionState(long accountId, String state);
@Query("DELETE FROM accounts WHERE pk_account_id = :accountId")
Completable deleteAccount(long accountId);
}

View File

@ -1,29 +0,0 @@
package org.mercury_im.messenger.persistence.room.dao;
import androidx.annotation.WorkerThread;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.room.model.RoomAvatarModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import io.reactivex.Maybe;
@Dao
@TypeConverters(EntityBareJidConverter.class)
@WorkerThread
public interface AvatarDao extends BaseDao<RoomAvatarModel> {
@Query("SELECT * FROM avatars WHERE pk_avatar_id = :avatarId")
Maybe<RoomAvatarModel> getAvatarById(long avatarId);
@Query("SELECT * FROM avatars WHERE fk_entity_id = :entityId")
Maybe<RoomAvatarModel> getAvatarByEntityId(long entityId);
@Query("SELECT avatars.* " +
"FROM avatars INNER JOIN entities ON avatars.fk_entity_id = entities.pk_entity_id " +
"WHERE entities.jid = :jid")
Maybe<RoomAvatarModel> getAvatarByJid(EntityBareJid jid);
}

View File

@ -1,40 +0,0 @@
package org.mercury_im.messenger.persistence.room.dao;
import androidx.room.Delete;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Transaction;
import androidx.room.Update;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;
public interface BaseDao<T> {
@Insert(onConflict = OnConflictStrategy.IGNORE)
Single<Long> insert(T entity);
@Insert(onConflict = OnConflictStrategy.IGNORE)
Single<Long[]> insert(T[] entities);
@Insert(onConflict = OnConflictStrategy.IGNORE)
Single<List<Long>> insert(List<T> entities);
@Update
Completable update(T entity);
@Update
Completable update(T[] entities);
@Delete
Completable delete(T entity);
@Delete
Completable delete(T[] entities);
@Delete
Completable delete(List<T> entities);
}

View File

@ -1,56 +0,0 @@
package org.mercury_im.messenger.persistence.room.dao;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.room.model.RoomChatModel;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import java.util.List;
import io.reactivex.Maybe;
import io.reactivex.Observable;
@Dao
@TypeConverters(EntityBareJidConverter.class)
public interface ChatDao extends BaseDao<RoomChatModel> {
@Query("SELECT * FROM chats")
Observable<List<RoomChatModel>> getAllChats();
@Query("SELECT chats.* FROM chats JOIN entities WHERE fk_account_id = :accountId")
Observable<List<RoomChatModel>> getAllChatsOfAccount(long accountId);
@Query("SELECT * FROM chats WHERE fk_entity_id = :entityId")
Maybe<RoomChatModel> maybeGetChatWithEntity(long entityId);
@Query("SELECT * FROM chats WHERE fk_entity_id = :entityId")
Observable<RoomChatModel> getChatWithEntity(long entityId);
@Query("SELECT chats.* FROM chats JOIN entities WHERE fk_account_id = :accountId AND jid = :jid")
Maybe<RoomChatModel> maybeGetChatWithJid(long accountId, EntityBareJid jid);
@Query("SELECT chats.* FROM chats JOIN entities WHERE fk_account_id = :accountId AND jid = :jid")
Observable<RoomChatModel> getChatWithJid(long accountId, EntityBareJid jid);
@Query("SELECT chats.* FROM chats JOIN contacts WHERE contacts.pk_contact_id = :contactId")
Maybe<RoomChatModel> maybeGetChatWithContact(long contactId);
@Query("SELECT chats.* FROM chats JOIN contacts WHERE contacts.pk_contact_id = :contactId")
Observable<RoomChatModel> getChatWithContact(long contactId);
@Query("SELECT chats.pk_chat_id as chatId, " +
"chats.fk_entity_id as entityId, " +
"contacts.rostername as contactName, " +
"entities.fk_account_id as accountId, " +
"jid, active " +
"from chats " +
"LEFT JOIN entities " +
"ON chats.fk_entity_id = entities.pk_entity_id " +
"LEFT JOIN contacts " +
"ON entities.pk_entity_id = contacts.fk_entity_id")
Observable<List<Chat>> getChatPojos();
}

View File

@ -1,79 +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.RoomContactModel;
import org.mercury_im.messenger.persistence.room.model.RoomEntityModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
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;
@Dao
@TypeConverters(EntityBareJidConverter.class)
public interface ContactDao extends BaseDao<RoomContactModel> {
@Override
@Insert(onConflict = REPLACE)
Single<Long> insert(RoomContactModel entity);
@Query("SELECT * FROM contacts WHERE pk_contact_id = :id")
Observable<RoomContactModel> getContact(long id);
@Query("SELECT * FROM contacts WHERE pk_contact_id = :id")
Maybe<RoomContactModel> maybeGetContact(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 " +
"ON contacts.fk_entity_id = entities.pk_entity_id " +
"WHERE entities.fk_account_id = :accountId AND jid = :jid")
Maybe<RoomContactModel> maybeGetContactByJid(long accountId, EntityBareJid jid);
@Query("SELECT contacts.* FROM contacts JOIN entities " +
"ON contacts.fk_entity_id = entities.pk_entity_id " +
"WHERE entities.fk_account_id = :accountId AND jid = :jid")
Observable<RoomContactModel> getContactByJid(long accountId, EntityBareJid jid);
@Query("SELECT contacts.* FROM contacts JOIN entities " +
"ON contacts.fk_entity_id = entities.pk_entity_id " +
"WHERE entities.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_entity_id IN " +
"(SELECT pk_entity_id FROM entities " +
"WHERE entities.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

@ -1,27 +0,0 @@
package org.mercury_im.messenger.persistence.room.dao;
import androidx.annotation.WorkerThread;
import androidx.room.Dao;
import androidx.room.Query;
import org.mercury_im.messenger.persistence.room.model.RoomEntityCapsModel;
import java.util.List;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;
@Dao
@WorkerThread
public interface EntityCapsDao extends BaseDao<RoomEntityCapsModel> {
@Query("SELECT * FROM entity_caps WHERE pk_node_ver = :nodeVer")
Single<RoomEntityCapsModel> getEntityCapsForNodeVer(String nodeVer);
@Query("SELECT * FROM entity_caps")
Observable<List<RoomEntityCapsModel>> getAllEntityCaps();
@Query("DELETE FROM entity_caps")
Completable deleteAllEntityCaps();
}

View File

@ -1,26 +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.RoomEntityModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import io.reactivex.Maybe;
import io.reactivex.Single;
import static androidx.room.OnConflictStrategy.REPLACE;
@Dao
@TypeConverters(EntityBareJidConverter.class)
public interface EntityDao extends BaseDao<RoomEntityModel> {
@Query("SELECT * FROM entities WHERE pk_entity_id = :id")
Maybe<RoomEntityModel> getEntity(long id);
@Query("SELECT * FROM entities WHERE fk_account_id = :accountId AND jid = :jid")
Maybe<RoomEntityModel> getEntityFor(long accountId, EntityBareJid jid);
}

View File

@ -1,56 +0,0 @@
package org.mercury_im.messenger.persistence.room.dao;
import androidx.room.Dao;
import androidx.room.Query;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.room.model.RoomMessageModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import java.util.List;
import io.reactivex.Observable;
@Dao
@TypeConverters(EntityBareJidConverter.class)
public interface MessageDao extends BaseDao<RoomMessageModel> {
@Query("SELECT * FROM messages")
Observable<List<RoomMessageModel>> getAllMessages();
@Query("SELECT * FROM messages " +
"WHERE fk_account_id = :accountId " +
"ORDER BY send_date ASC")
Observable<List<RoomMessageModel>> getAllMessagesOf(long accountId);
@Query("SELECT * FROM messages " +
"WHERE fk_account_id = :accountId AND `from` = :sender " +
"ORDER BY send_date ASC")
Observable<List<RoomMessageModel>> getAllMessagesFrom(long accountId, EntityBareJid sender);
@Query("SELECT * FROM messages " +
"WHERE fk_account_id = :accountId AND (`from` = :peer OR `to` = :peer) " +
"ORDER BY send_date ASC")
Observable<List<RoomMessageModel>> getAllMessagesInConversation(long accountId, EntityBareJid peer);
@Query("SELECT * FROM messages " +
"WHERE body LIKE :query " +
"COLLATE utf8_general_ci") // case insensitive
Observable<List<RoomMessageModel>> findMessageByQuery(String query);
@Query("SELECT * FROM messages " +
"WHERE fk_account_id = :accountId AND body LIKE :query " +
"COLLATE utf8_general_ci") // case insensitive
Observable<List<RoomMessageModel>> findMessageByQuery(long accountId, String query);
@Query("SELECT * FROM messages " +
"WHERE fk_account_id = :accountId " +
"AND (`from` = :peer OR `to` = :peer) " +
"AND body LIKE :query " +
"COLLATE utf8_general_ci") // case insensitive
Observable<List<RoomMessageModel>> findMessageByQuery(long accountId, EntityBareJid peer, String query);
@Query("SELECT * FROM messages WHERE fk_account_id = :accountId AND (`from` = :peer OR `to` = :peer) ORDER BY send_date DESC LIMIT 1")
Observable<RoomMessageModel> getLastMessageFrom(long accountId, EntityBareJid peer);
}

View File

@ -1,27 +0,0 @@
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,107 +0,0 @@
package org.mercury_im.messenger.persistence.room.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.AbstractAccountModel;
import org.mercury_im.messenger.persistence.model.AccountModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import static org.mercury_im.messenger.persistence.room.model.RoomAccountModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomAccountModel.TABLE;
@Entity(tableName = TABLE, indices = {@Index(KEY_ID)})
public class RoomAccountModel extends AbstractAccountModel {
public static final String TABLE = "accounts";
public static final String KEY_ID = "pk_account_id";
public static final String KEY_JID = "jid";
public static final String KEY_PASSWORD = "password";
public static final String KEY_ENABLED = "enabled";
public static final String KEY_STATE = "state";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = KEY_ID)
public long id;
@TypeConverters(EntityBareJidConverter.class)
@ColumnInfo(name = KEY_JID)
private EntityBareJid jid;
@ColumnInfo(name = KEY_PASSWORD)
private String password;
@ColumnInfo(name = KEY_ENABLED)
private boolean enabled;
@ColumnInfo(name = KEY_STATE)
private String state;
@Override
public long getId() {
return id;
}
@Override
public void setId(long id) {
this.id = id;
}
@Override
public EntityBareJid getJid() {
return jid;
}
@Override
public void setJid(EntityBareJid jid) {
this.jid = jid;
}
@Override
public String getPassword() {
return password;
}
@Override
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean getEnabled() {
return enabled;
}
@Override
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
@Override
public String getState() {
return state;
}
@Override
public void setState(String state) {
this.state = state;
}
@Override
@NonNull
public String toString() {
return "AccountModel[" +
KEY_ID + ": " + getId() + ", " +
KEY_JID + ": " + (getJid() != null ? getJid().toString() : "null") + ", " +
KEY_PASSWORD + ": " + getPassword() + ", " +
KEY_ENABLED + ": " + getEnabled() + ", " +
KEY_STATE + ": " +getState() +
"]";
}
}

View File

@ -1,87 +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 org.mercury_im.messenger.persistence.model.AvatarModel;
import static androidx.room.ForeignKey.RESTRICT;
import static org.mercury_im.messenger.persistence.room.model.RoomAvatarModel.KEY_ENTITY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomAvatarModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomAvatarModel.TABLE;
@Entity(tableName = TABLE,
indices = {
@Index(value = KEY_ID),
@Index(value = KEY_ENTITY_ID, unique = true)
},
foreignKeys = {
@ForeignKey(entity = RoomEntityModel.class,
parentColumns = RoomEntityModel.KEY_ID,
childColumns = KEY_ENTITY_ID,
onDelete = RESTRICT)
})
public class RoomAvatarModel implements AvatarModel {
public static final String TABLE = "avatars";
public static final String KEY_ID = "pk_avatar_id";
public static final String KEY_ENTITY_ID = "fk_entity_id";
public static final String KEY_SHA1_SUM = "sha1sum";
public static final String KEY_BYTES = "bytes";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = KEY_ID)
private long avatarId;
@ColumnInfo(name = KEY_ENTITY_ID)
private long entityId;
@ColumnInfo(name = KEY_SHA1_SUM)
private String sha1Sum;
@ColumnInfo(name = KEY_BYTES)
private byte[] bytes;
@Override
public long getAvatarId() {
return avatarId;
}
@Override
public void setAvatarId(long id) {
this.avatarId = id;
}
@Override
public long entityId() {
return entityId;
}
@Override
public void setEntityId(long entityId) {
this.entityId = entityId;
}
@Override
public String getSha1Sum() {
return sha1Sum;
}
@Override
public void setSha1Sum(String sha1Sum) {
this.sha1Sum = sha1Sum;
}
@Override
public byte[] getBytes() {
return bytes.clone();
}
@Override
public void setBytes(byte[] bytes) {
this.bytes = bytes;
}
}

View File

@ -1,126 +0,0 @@
package org.mercury_im.messenger.persistence.room.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import org.mercury_im.messenger.persistence.model.ChatModel;
import static androidx.room.ForeignKey.CASCADE;
import static org.mercury_im.messenger.persistence.room.model.RoomChatModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomChatModel.KEY_ENTITY;
import static org.mercury_im.messenger.persistence.room.model.RoomChatModel.TABLE;
@Entity(tableName = TABLE,
indices = {
@Index(KEY_ID), @Index(KEY_ENTITY)
},
foreignKeys = {
@ForeignKey(entity = RoomEntityModel.class,
parentColumns = RoomEntityModel.KEY_ID,
childColumns = KEY_ENTITY,
onDelete = CASCADE)
})
public class RoomChatModel implements ChatModel {
public static final String TABLE = "chats";
public static final String KEY_ID = "pk_chat_id";
public static final String KEY_ENTITY = "fk_entity_id";
public static final String KEY_ACTIVE = "active";
public static final String KEY_LAST_READ_MSG = "last_read_message";
public static final String KEY_MOST_RECENT_MAM_MSG = "most_recent_mam_msg";
public static final String KEY_EARLIEST_MAM_MSG = "earliest_mam_msg";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = KEY_ID)
private long id;
@ColumnInfo(name = KEY_ENTITY)
private long peerEntityId;
@ColumnInfo(name = KEY_ACTIVE)
private boolean isActive;
@ColumnInfo(name = KEY_LAST_READ_MSG)
private long lastReadMessageId;
@ColumnInfo(name = KEY_MOST_RECENT_MAM_MSG)
private String mostRecentMamMessageId;
@ColumnInfo(name = KEY_EARLIEST_MAM_MSG)
private String earliestMamMessageId;
@Override
public long getId() {
return id;
}
@Override
public void setId(long id) {
this.id = id;
}
@Override
public long getPeerEntityId() {
return peerEntityId;
}
@Override
public void setPeerEntityId(long id) {
this.peerEntityId = id;
}
@Override
public boolean isActive() {
return isActive;
}
@Override
public void setActive(boolean active) {
this.isActive = active;
}
@Override
public long getLastReadMessageId() {
return lastReadMessageId;
}
@Override
public void setLastReadMessageId(long messageId) {
this.lastReadMessageId = messageId;
}
@Override
public String getMostRecentMamMessageId() {
return mostRecentMamMessageId;
}
@Override
public void setMostRecentMamMessageId(String uid) {
this.mostRecentMamMessageId = uid;
}
@Override
public String getEarliestMamMessageId() {
return earliestMamMessageId;
}
@Override
public void setEarliestMamMessageId(String uid) {
this.earliestMamMessageId = uid;
}
@Override
@NonNull
public String toString() {
return "ChatModel[" +
KEY_ID + ": " + getId() + ", " +
KEY_ENTITY + ": " + getPeerEntityId() + ", " +
KEY_ACTIVE + ": " + isActive() +
"]";
}
}

View File

@ -1,153 +0,0 @@
package org.mercury_im.messenger.persistence.room.model;
import androidx.annotation.NonNull;
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;
import static androidx.room.ForeignKey.RESTRICT;
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;
@Entity(tableName = TABLE,
indices = {
@Index(value = KEY_ID),
@Index(value = KEY_ENTITY_ID, unique = true),
@Index(value = {KEY_ID, KEY_ENTITY_ID}, unique = true)
},
foreignKeys = {
@ForeignKey(entity = RoomEntityModel.class,
parentColumns = RoomEntityModel.KEY_ID,
childColumns = KEY_ENTITY_ID,
onDelete = RESTRICT)})
public class RoomContactModel implements ContactModel {
public static final String TABLE = "contacts";
public static final String KEY_ID = "pk_contact_id";
public static final String KEY_ENTITY_ID = "fk_entity_id";
public static final String KEY_ROSTER_NAME = "rostername";
public static final String KEY_SUB_DIRECTION = "sub_direction";
public static final String KEY_SUB_PENDING = "sub_pending";
public static final String KEY_SUB_APPROVED = "sub_approved";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = KEY_ID)
private long id;
@ColumnInfo(name = KEY_ENTITY_ID)
private long entityId;
@ColumnInfo(name = KEY_ROSTER_NAME)
private String rosterName;
@ColumnInfo(name = KEY_SUB_DIRECTION)
@TypeConverters(DirectionConverter.class)
private DIRECTION direction;
@ColumnInfo(name = KEY_SUB_PENDING)
private boolean subscriptionPending;
@ColumnInfo(name = KEY_SUB_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 long getId() {
return id;
}
@Override
public void setId(long id) {
this.id = id;
}
@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 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;
this.entityId = entity.getId();
}
@Override
@NonNull
public String toString() {
return "ContactModel[" +
KEY_ID + ": " + getId() + ", " +
KEY_ENTITY_ID + ": " + getEntityId() + ", " +
KEY_ROSTER_NAME + ": " + getRosterName() + ", " +
KEY_SUB_DIRECTION + ": " + getDirection() + ", " +
KEY_SUB_PENDING + ": " + isSubscriptionPending() + ", " +
KEY_SUB_APPROVED + ": " + isApproved() +
"]";
}
}

View File

@ -1,51 +0,0 @@
package org.mercury_im.messenger.persistence.room.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import org.mercury_im.messenger.persistence.model.EntityCapsModel;
import static org.mercury_im.messenger.persistence.room.model.RoomEntityCapsModel.TABLE;
@Entity(tableName = TABLE)
public class RoomEntityCapsModel implements EntityCapsModel {
public static final String TABLE = "entity_caps";
public static final String KEY_NODE_VER = "pk_node_ver";
public static final String KEY_XML = "xml";
@PrimaryKey
@NonNull
@ColumnInfo(name = KEY_NODE_VER, index = true)
private String nodeVer;
@ColumnInfo(name = KEY_XML)
private String xml;
public RoomEntityCapsModel(@NonNull String nodeVer) {
this.nodeVer = nodeVer;
}
@Override
@NonNull
public String getNodeVer() {
return nodeVer;
}
@Override
public void setNodeVer(@NonNull String nodeVer) {
this.nodeVer = nodeVer;
}
@Override
public String getXml() {
return xml;
}
@Override
public void setXml(String xml) {
this.xml = xml;
}
}

View File

@ -1,94 +0,0 @@
package org.mercury_im.messenger.persistence.room.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import androidx.room.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import org.mercury_im.messenger.persistence.room.type_converter.FileConverter;
import java.io.File;
import static androidx.room.ForeignKey.CASCADE;
import static org.mercury_im.messenger.persistence.room.model.RoomEntityModel.KEY_ACCOUNT_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomEntityModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomEntityModel.KEY_JID;
import static org.mercury_im.messenger.persistence.room.model.RoomEntityModel.TABLE;
@Entity(tableName = TABLE,
indices = {
@Index(value = KEY_ID),
@Index(value = {KEY_ACCOUNT_ID, KEY_JID}, unique = true)
},
foreignKeys = {
@ForeignKey(entity = RoomAccountModel.class,
parentColumns = RoomAccountModel.KEY_ID,
childColumns = KEY_ACCOUNT_ID,
onDelete = CASCADE)
})
public class RoomEntityModel implements EntityModel {
public static final String TABLE = "entities";
public static final String KEY_ID = "pk_entity_id";
public static final String KEY_ACCOUNT_ID = "fk_account_id";
public static final String KEY_JID = "jid";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = KEY_ID)
protected long id;
@ColumnInfo(name = KEY_ACCOUNT_ID)
protected long accountId;
@NonNull
@TypeConverters(EntityBareJidConverter.class)
@ColumnInfo(name = KEY_JID)
protected EntityBareJid jid;
@Override
public long getId() {
return id;
}
@Override
public void setId(long id) {
this.id = id;
}
@NonNull
@Override
public EntityBareJid getJid() {
return jid;
}
@Override
public void setJid(@NonNull EntityBareJid jid) {
this.jid = jid;
}
@Override
public long getAccountId() {
return accountId;
}
@Override
public void setAccountId(long accountId) {
this.accountId = accountId;
}
@Override
@NonNull
public String toString() {
return "EntityModel[" +
KEY_ID + ": " + getId() + ", " +
KEY_ACCOUNT_ID + ": " + getAccountId() + ", " +
KEY_JID + ": " + getJid().toString() +
"]";
}
}

View File

@ -1,141 +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.TypeConverters;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.MessageModel;
import org.mercury_im.messenger.persistence.room.type_converter.DateConverter;
import org.mercury_im.messenger.persistence.room.type_converter.EntityBareJidConverter;
import java.util.Date;
import static androidx.room.ForeignKey.CASCADE;
import static org.mercury_im.messenger.persistence.room.model.RoomMessageModel.KEY_ACCOUNT_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomMessageModel.KEY_ID;
import static org.mercury_im.messenger.persistence.room.model.RoomMessageModel.TABLE;
@Entity(tableName = TABLE,
foreignKeys = {
@ForeignKey(entity = RoomAccountModel.class,
parentColumns = RoomAccountModel.KEY_ID,
childColumns = KEY_ACCOUNT_ID,
onDelete = CASCADE)},
indices = {
@Index(KEY_ID),
@Index(KEY_ACCOUNT_ID)
})
public class RoomMessageModel implements MessageModel {
public static final String TABLE = "messages";
public static final String KEY_ID = "pk_message_id";
public static final String KEY_ACCOUNT_ID = "fk_account_id";
public static final String KEY_BODY = "body";
public static final String KEY_SEND_DATE = "send_date";
public static final String KEY_FROM = "from";
public static final String KEY_TO = "to";
public static final String KEY_INCOMING = "incoming";
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = KEY_ID)
private long id;
@ColumnInfo(name = KEY_ACCOUNT_ID)
private long accountId;
@ColumnInfo(name = KEY_BODY)
private String body;
@TypeConverters(DateConverter.class)
@ColumnInfo(name = KEY_SEND_DATE)
private Date sendDate;
@TypeConverters(EntityBareJidConverter.class)
@ColumnInfo(name = KEY_FROM)
private EntityBareJid from;
@TypeConverters(EntityBareJidConverter.class)
@ColumnInfo(name = KEY_TO)
private EntityBareJid to;
@ColumnInfo(name = KEY_INCOMING)
private boolean incoming;
public RoomMessageModel() {
}
@Override
public long getId() {
return id;
}
@Override
public void setId(long val) {
this.id = val;
}
@Override
public long getAccountId() {
return accountId;
}
@Override
public void setAccountId(long accountId) {
this.accountId = accountId;
}
@Override
public String getBody() {
return body;
}
@Override
public void setBody(String body) {
this.body = body;
}
@Override
public Date getSendDate() {
return sendDate;
}
@Override
public void setSendDate(Date date) {
this.sendDate = date;
}
@Override
public EntityBareJid getFrom() {
return from;
}
@Override
public void setFrom(EntityBareJid sender) {
this.from = sender;
}
@Override
public EntityBareJid getTo() {
return to;
}
@Override
public void setTo(EntityBareJid recipient) {
this.to = recipient;
}
@Override
public boolean isIncoming() {
return incoming;
}
@Override
public void setIncoming(boolean isIncoming) {
this.incoming = isIncoming;
}
}

View File

@ -1,58 +0,0 @@
package org.mercury_im.messenger.persistence.room.model;
import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.Index;
import androidx.room.PrimaryKey;
import org.mercury_im.messenger.persistence.model.RosterInformationModel;
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 RoomRosterInformationModel implements RosterInformationModel {
public static final String TABLE = "roster_information";
public static final String KEY_ID = "pk_account_id";
public static final String KEY_ROSTER_VERSION = "roster_version";
@PrimaryKey
@ColumnInfo(name = KEY_ID)
private long accountId;
@ColumnInfo(name = KEY_ROSTER_VERSION)
private String rosterVersion;
@Override
public long getAccountId() {
return accountId;
}
@Override
public void setAccountId(long accountId) {
this.accountId = accountId;
}
@Override
public String getRosterVersion() {
return rosterVersion;
}
@Override
public void setRosterVersion(String rosterVersion) {
this.rosterVersion = rosterVersion;
}
@Override
@NonNull
public String toString() {
return "RosterInformationModel[" +
KEY_ID + ": " + getAccountId() + ", " +
KEY_ROSTER_VERSION + ": " + getRosterVersion() +
"]";
}
}

View File

@ -1,77 +0,0 @@
package org.mercury_im.messenger.persistence.room.repository;
import android.util.Log;
import androidx.annotation.NonNull;
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;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import static org.mercury_im.messenger.persistence.room.AppDatabase.TAG;
public class IAccountRepository implements AccountRepository<RoomAccountModel> {
private final AccountDao accountDao;
@Inject
public IAccountRepository(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public RoomAccountModel newAccountModel() {
return new RoomAccountModel();
}
@Override
public Observable<RoomAccountModel> getAccount(long accountId) {
return accountDao.getAccountById(accountId);
}
@Override
public Maybe<RoomAccountModel> maybeGetAccount(long accountId) {
return accountDao.maybeGetAccountById(accountId);
}
@Override
public Observable<List<RoomAccountModel>> getAllAccounts() {
return accountDao.getAllAccounts();
}
@Override
public Single<Long> insertAccount(@NonNull RoomAccountModel accountModel) {
return accountDao.insert(accountModel)
.map(accountId -> {
accountModel.setId(accountId);
return accountId;
})
.doOnSubscribe(ignore -> Log.v(TAG, "Insert " + accountModel))
.doAfterSuccess(accountId -> Log.v(TAG, "AccountModel has new ID " + accountId));
}
@Override
public Completable updateAccount(RoomAccountModel accountModel) {
return accountDao.update(accountModel);
}
@Override
public Completable deleteAccount(long accountId) {
return accountDao.deleteAccount(accountId);
}
@Override
public Completable deleteAccount(RoomAccountModel item) {
return accountDao.delete(item);
}
}

View File

@ -1,46 +0,0 @@
package org.mercury_im.messenger.persistence.room.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.repository.AvatarRepository;
import org.mercury_im.messenger.persistence.room.dao.AvatarDao;
import org.mercury_im.messenger.persistence.room.model.RoomAvatarModel;
import javax.inject.Inject;
import io.reactivex.Maybe;
import io.reactivex.Single;
public class IAvatarRepository implements AvatarRepository<RoomAvatarModel> {
private final AvatarDao dao;
@Inject
public IAvatarRepository(AvatarDao dao) {
this.dao = dao;
}
@Override
public RoomAvatarModel newAvatarModel() {
return new RoomAvatarModel();
}
@Override
public Maybe<RoomAvatarModel> getAvatarById(long avatarId) {
return dao.getAvatarById(avatarId);
}
@Override
public Maybe<RoomAvatarModel> getAvatarByEntityId(long entityId) {
return dao.getAvatarByEntityId(entityId);
}
@Override
public Maybe<RoomAvatarModel> getAvatarByJid(EntityBareJid jid) {
return dao.getAvatarByJid(jid);
}
@Override
public Single<Long> updateOrInsertAvatar(RoomAvatarModel avatarModel) {
return dao.insert(avatarModel);
}
}

View File

@ -1,120 +0,0 @@
package org.mercury_im.messenger.persistence.room.repository;
import android.util.Log;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.model.EntityModel;
import org.mercury_im.messenger.persistence.pojo.Chat;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.persistence.room.dao.ChatDao;
import org.mercury_im.messenger.persistence.room.model.RoomChatModel;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import static org.mercury_im.messenger.persistence.room.AppDatabase.TAG;
public class IChatRepository implements ChatRepository<RoomChatModel> {
private final ChatDao chatDao;
private final RosterRepository rosterRepository;
@Inject
public IChatRepository(ChatDao chatDao, RosterRepository rosterRepository) {
this.chatDao = chatDao;
this.rosterRepository = rosterRepository;
}
@Override
public RoomChatModel newChatModel() {
return new RoomChatModel();
}
@Override
public Observable<RoomChatModel> getOrCreateChatWith(long accountId, EntityBareJid jid) {
return Observable.fromCallable(() -> {
EntityModel entity = (EntityModel) rosterRepository.getOrCreateEntityForAccountAndJid(accountId, jid).blockingGet();
RoomChatModel chat = maybeGetChatWithEntity(entity).blockingGet();
if (chat == null) {
chat = newChatModel();
chat.setPeerEntityId(entity.getId());
chat.setId(insertChat(chat).blockingGet());
}
return chat;
}).concatWith(getChatWith(accountId, jid).skip(1));
}
@Override
public Observable<RoomChatModel> getChatWith(long accountId, EntityBareJid jid) {
return chatDao.getChatWithJid(accountId, jid);
}
@Override
public Observable<RoomChatModel> getChatWithEntity(long entityId) {
return chatDao.getChatWithEntity(entityId);
}
@Override
public Completable updateChat(RoomChatModel chat) {
return chatDao.update(chat);
}
@Override
public Observable<RoomChatModel> getChatWithContact(long contactId) {
return chatDao.getChatWithContact(contactId);
}
@Override
public Observable<List<RoomChatModel>> getAllChats() {
return chatDao.getAllChats();
}
@Override
public Observable<List<RoomChatModel>> getAllChatsOfAccount(long accountId) {
return chatDao.getAllChatsOfAccount(accountId);
}
@Override
public Maybe<RoomChatModel> maybeGetChatWith(long accountId, EntityBareJid jid) {
return chatDao.maybeGetChatWithJid(accountId, jid);
}
@Override
public Maybe<RoomChatModel> maybeGetChatWithEntity(long entityId) {
return chatDao.maybeGetChatWithEntity(entityId);
}
@Override
public Maybe<RoomChatModel> maybeGetChatWithContact(long contactId) {
return chatDao.maybeGetChatWithContact(contactId);
}
@Override
public Single<Long> insertChat(RoomChatModel chat) {
return chatDao.insert(chat)
.map(chatId -> {
chat.setId(chatId);
return chatId;
})
.doOnSubscribe(ignore -> Log.v(TAG, "Insert " + chat))
.doAfterSuccess(chatId -> Log.v(TAG, "Assign ID " + chatId + " to " + chat));
}
@Override
public Completable closeChat(RoomChatModel chat) {
return chatDao.delete(chat);
}
@Override
public Observable<List<Chat>> getDisplayableChats() {
return chatDao.getChatPojos();
}
}

View File

@ -1,66 +0,0 @@
package org.mercury_im.messenger.persistence.room.repository;
import androidx.annotation.NonNull;
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;
import org.mercury_im.messenger.persistence.room.dao.EntityCapsDao;
import org.mercury_im.messenger.persistence.room.model.RoomEntityCapsModel;
import java.util.List;
import javax.inject.Inject;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;
public class IEntityCapsRepository implements EntityCapsRepository<RoomEntityCapsModel> {
private final EntityCapsDao dao;
@Inject
public IEntityCapsRepository(EntityCapsDao dao) {
this.dao = dao;
}
@Override
public RoomEntityCapsModel newEntityCapsModel(@NonNull String nodeVer) {
return new RoomEntityCapsModel(nodeVer);
}
@Override
public Observable<List<RoomEntityCapsModel>> getAllEntityCaps() {
return dao.getAllEntityCaps();
}
@Override
public Single<RoomEntityCapsModel> getEntityCapsForNodeVer(String nodeVer) {
return dao.getEntityCapsForNodeVer(nodeVer);
}
@Override
public Single<List<Long>> insertOrReplaceEntityCaps(List<RoomEntityCapsModel> entityCaps) {
return dao.insert(entityCaps);
}
@Override
public Single<Long> insertOrReplaceEntityCaps(RoomEntityCapsModel entityCaps) {
return dao.insert(entityCaps);
}
@Override
public Completable deleteOrReplaceEntityCaps(List<RoomEntityCapsModel> entityCaps) {
return dao.delete(entityCaps);
}
@Override
public Completable deleteOrReplaceEntityCaps(RoomEntityCapsModel entityCaps) {
return dao.delete(entityCaps);
}
@Override
public Completable deleteAllEntityCaps() {
return dao.deleteAllEntityCaps();
}
}

View File

@ -1,84 +0,0 @@
package org.mercury_im.messenger.persistence.room.repository;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.repository.MessageRepository;
import org.mercury_im.messenger.persistence.room.dao.MessageDao;
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;
public class IMessageRepository implements MessageRepository<RoomMessageModel> {
private final MessageDao messageDao;
@Inject
public IMessageRepository(MessageDao messageDao) {
this.messageDao = messageDao;
}
@Override
public RoomMessageModel newMessageModel() {
return new RoomMessageModel();
}
@Override
public Maybe<RoomMessageModel> getMessage(long accountId, long chatId, long messageId) {
return null;
}
@Override
public Single<Long> insertMessage(RoomMessageModel message) {
return messageDao.insert(message);
}
@Override
public Single<List<Long>> insertMessages(List<RoomMessageModel> messages) {
return messageDao.insert(messages);
}
@Override
public Observable<RoomMessageModel> getLastMessageFrom(long accountId, EntityBareJid peer) {
return messageDao.getLastMessageFrom(accountId, peer);
}
@Override
public Observable<List<RoomMessageModel>> getAllMessages() {
return messageDao.getAllMessages();
}
@Override
public Observable<List<RoomMessageModel>> getAllMessagesOf(long accountId) {
return messageDao.getAllMessagesOf(accountId);
}
@Override
public Observable<List<RoomMessageModel>> getAllMessagesFrom(long accountId, EntityBareJid contact) {
return messageDao.getAllMessagesFrom(accountId, contact);
}
@Override
public Observable<List<RoomMessageModel>> getAllMessagesOfChat(long accountId, EntityBareJid peer) {
return messageDao.getAllMessagesInConversation(accountId, peer);
}
@Override
public Observable<List<RoomMessageModel>> findMessageByQuery(String query) {
return messageDao.findMessageByQuery("%" + query + "%");
}
@Override
public Observable<List<RoomMessageModel>> findMessageByQuery(long accountId, String query) {
return messageDao.findMessageByQuery(accountId, "%" + query + "%");
}
@Override
public Observable<List<RoomMessageModel>> findMessageByQuery(long accountId, EntityBareJid peer, String query) {
return messageDao.findMessageByQuery(accountId, peer, "%" + query + "%");
}
}

View File

@ -1,263 +0,0 @@
package org.mercury_im.messenger.persistence.room.repository;
import android.util.Log;
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.Maybe;
import io.reactivex.Observable;
import io.reactivex.Single;
import static org.mercury_im.messenger.persistence.room.AppDatabase.TAG;
public class IRosterRepository extends RosterRepository<RoomEntityModel, RoomContactModel, RoomRosterInformationModel> {
private final EntityDao entityDao;
@Override
public Observable<RoomContactModel> getContact(long accountId, EntityBareJid jid) {
return contactDao.getContactByJid(accountId, jid);
}
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> upsertContact(RoomContactModel contact) {
return Single.fromCallable(() -> {
RoomEntityModel existingEntityModel = entityDao
.getEntityFor(contact.getEntity().getAccountId(), contact.getEntity().getJid())
.blockingGet();
if (existingEntityModel == null) {
// Insert missing entity
existingEntityModel = (RoomEntityModel) contact.getEntity();
long entityId = insertEntity(existingEntityModel).blockingGet();
existingEntityModel.setId(entityId);
contact.setEntity(existingEntityModel);
} else {
contact.getEntity().setId(existingEntityModel.getId());
contact.setEntityId(existingEntityModel.getId());
entityDao.update((RoomEntityModel) contact.getEntity())
.doOnSubscribe(ignore -> Log.v(TAG, "Updating entity " + contact.getEntity()))
.blockingAwait();
}
RoomContactModel existingContactModel = contactDao.getContactForEntityId(existingEntityModel.getId()).blockingGet();
if (existingContactModel == null) {
// Insert missing contact
existingContactModel = contact;
return insertContact(existingContactModel).blockingGet();
} else {
contact.setId(existingContactModel.getId());
contactDao.update(contact)
.doOnSubscribe(ignore -> Log.v(TAG, "Updating contact " + contact))
.doOnComplete(() -> Log.v(TAG, "Update complete"))
.blockingAwait();
}
return existingContactModel.getId();
});
}
@Override
public Single<Long> insertContact(RoomContactModel contact) {
return contactDao.insert(contact)
.map(contactId -> {
contact.setId(contactId);
return contactId;
})
.doOnSubscribe(ignore -> Log.v(TAG, "Insert " + contact))
.doAfterSuccess(cid -> Log.v(TAG, "Assigned ID " + cid + " to " + contact));
}
@Override
public Single<RoomEntityModel> getEntityForContact(long contactId) {
Single<RoomEntityModel> s = contactDao.getEntityForContactId(contactId);
return s;
}
@Override
public Observable<RoomContactModel> getContact(long contactId) {
return contactDao.getContact(contactId)
.map(contact -> {
contact.setEntity(getEntityForContact(contactId).toMaybe().blockingGet());
contact.setEntityId(contact.getEntity().getId());
return contact;
});
}
@Override
public Maybe<RoomContactModel> maybeGetContact(long id) {
return contactDao.maybeGetContact(id)
// Set the entity
.zipWith(getEntityForContact(id).toMaybe(),
(contact, entity) -> {
contact.setEntity(entity);
contact.setEntityId(entity.getId());
return contact;
});
}
@Override
public Single<RoomEntityModel> getOrCreateEntityForAccountAndJid(long accountId, EntityBareJid jid) {
return Single.fromCallable(() -> {
RoomEntityModel existing = getEntityForAccountAndJid(accountId, jid).blockingGet();
if (existing == null) {
existing = newEntityModel();
existing.setAccountId(accountId);
existing.setJid(jid);
existing.setId(insertEntity(existing).blockingGet());
}
return existing;
});
}
@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 = getEntityForAccountAndJid(accountId, jid.asEntityBareJidOrThrow());
return entity.flatMapCompletable(entityModel -> 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)
.doOnSubscribe(ignore -> Log.v(TAG, "Insert " + rosterInformation));
}
/*
RoomEntityModel
*/
@Override
public RoomEntityModel newEntityModel() {
return new RoomEntityModel();
}
@Override
public Maybe<RoomEntityModel> getEntity(long id) {
return entityDao.getEntity(id);
}
@Override
public Maybe<RoomEntityModel> getEntityForAccountAndJid(long accountId, EntityBareJid jid) {
return entityDao.getEntityFor(accountId, jid);
}
public Single<Long> insertEntity(RoomEntityModel entityModel) {
return entityDao.insert(entityModel)
.map(entityId -> {
entityModel.setId(entityId);
return entityId;
})
.doOnSubscribe(ignore -> Log.v(TAG, "Insert " + entityModel))
.doAfterSuccess(entityId -> Log.v(TAG, "Assign ID " + entityId + " to " + entityModel));
}
}

View File

@ -1,18 +0,0 @@
package org.mercury_im.messenger.persistence.room.type_converter;
import androidx.room.TypeConverter;
import java.util.Date;
public class DateConverter {
@TypeConverter
public static long toLong(Date date) {
return date != null ? date.getTime() : -1;
}
@TypeConverter
public static Date toDate(long lon) {
return lon != -1 ? new Date(lon) : null;
}
}

View File

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

View File

@ -1,25 +0,0 @@
package org.mercury_im.messenger.persistence.room.type_converter;
import androidx.room.TypeConverter;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import org.jxmpp.stringprep.XmppStringprepException;
public class EntityBareJidConverter {
@TypeConverter
public static String toString(EntityBareJid jid) {
return jid != null ? jid.toString() : null;
}
@TypeConverter
public static EntityBareJid toEntityBareJid(String string) {
try {
return string != null ? JidCreate.entityBareFrom(string) : null;
} catch (XmppStringprepException e) {
e.printStackTrace();
return null;
}
}
}

View File

@ -1,24 +0,0 @@
package org.mercury_im.messenger.persistence.room.type_converter;
import androidx.room.TypeConverter;
import java.io.File;
public class FileConverter {
@TypeConverter
public static String toString(File file) {
if (file == null) {
return null;
}
return file.getAbsolutePath();
}
@TypeConverter
public static File toFile(String string) {
if (string == null) {
return null;
}
return new File(string);
}
}

View File

@ -1,3 +0,0 @@
<resources>
<string name="app_name">RoomDatabase</string>
</resources>

View File

@ -1,17 +0,0 @@
package org.mercury_im.messenger.persistence.room;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}

View File

@ -1,19 +0,0 @@
# Abstract Persistence Layer of Mercury
This Android module defines interfaces for a persistence backend.
Ideally this module would at some point be a plain java (non-Android) module to allow for
non-Android implementations. This is currently being blocked by [LiveData](https://developer.android.com/topic/libraries/architecture/livedata)
being an Android library. We could fix this by replacing LiveData with RxJava, but this would
require us to manually handle LifeCycles of ViewModels etc. so for now we stick with LiveData.
## Packages
### `model` package
Contains interfaces that define the structure of data classes.
### `repository` package
Repositories build a user-friendly interface to query and modify data in the backend.
## Implementations
Currently the module is only being implemented by the `persistence-room` Android module.

View File

@ -1,12 +1,15 @@
apply plugin: 'java-library'
// Add the generated folder to the source directories so that we can work with generated classes
// This is apparently necessary for use with requery.
sourceSets {
main.java.srcDirs += "${buildDir}/generated/sources/annotationProcessor/java/main/"
}
dependencies {
api('org.jxmpp:jxmpp-jid') {
version {
prefer 'latest.release'
}
}
// JXMPP for Jid types. Version comes from smacks version.gradle
api("org.jxmpp:jxmpp-jid:$jxmppVersion")
// RxJava2
api "io.reactivex.rxjava2:rxjava:$rxJava2Version"
@ -15,5 +18,14 @@ dependencies {
implementation "com.google.dagger:dagger:$daggerVersion"
annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
// Requery ORM
api "io.requery:requery:$requeryVersion"
annotationProcessor "io.requery:requery-processor:$requeryVersion"
// JUnit for testing
testImplementation "junit:junit:$junitVersion"
implementation project(':thread_utils')
}
sourceCompatibility = "8"
targetCompatibility = "8"

View File

@ -1,21 +0,0 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -1,27 +0,0 @@
package org.mercury_im.messenger.persistence;
import android.content.Context;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();
assertEquals("org.mercury_im.messenger.persistence.test", appContext.getPackageName());
}
}

View File

@ -1,2 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.mercury_im.messenger.persistence" />

View File

@ -0,0 +1,33 @@
package org.mercury_im.messenger.persistence.converter;
import org.jxmpp.jid.EntityBareJid;
import org.jxmpp.jid.impl.JidCreate;
import io.requery.Converter;
public class EntityBareJidConverter implements Converter<EntityBareJid, String> {
@Override
public Class<EntityBareJid> getMappedType() {
return EntityBareJid.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(EntityBareJid jid) {
return jid == null ? null : jid.asUnescapedString();
}
@Override
public EntityBareJid convertToMapped(Class<? extends EntityBareJid> aClass, String string) {
return string == null ? null : JidCreate.entityBareFromOrThrowUnchecked(string);
}
}

View File

@ -0,0 +1,32 @@
package org.mercury_im.messenger.persistence.converter;
import org.mercury_im.messenger.persistence.enums.SaslCondition;
import io.requery.Converter;
public class SaslConditionConverter implements Converter<SaslCondition, String> {
@Override
public Class<SaslCondition> getMappedType() {
return SaslCondition.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(SaslCondition value) {
return value != null ? value.toString() : null;
}
@Override
public SaslCondition convertToMapped(Class<? extends SaslCondition> type, String value) {
return value != null ? SaslCondition.valueOf(value) : null;
}
}

View File

@ -0,0 +1,32 @@
package org.mercury_im.messenger.persistence.converter;
import org.mercury_im.messenger.persistence.enums.SubscriptionDirection;
import io.requery.Converter;
public class SubscriptionDirectionConverter implements Converter<SubscriptionDirection, String> {
@Override
public Class<SubscriptionDirection> getMappedType() {
return SubscriptionDirection.class;
}
@Override
public Class<String> getPersistedType() {
return String.class;
}
@Override
public Integer getPersistedSize() {
return null;
}
@Override
public String convertToPersisted(SubscriptionDirection subscriptionDirection) {
return subscriptionDirection == null ? null : subscriptionDirection.name();
}
@Override
public SubscriptionDirection convertToMapped(Class<? extends SubscriptionDirection> aClass, String string) {
return string == null ? null : SubscriptionDirection.valueOf(string);
}
}

View File

@ -0,0 +1,52 @@
package org.mercury_im.messenger.persistence.di;
import org.mercury_im.messenger.persistence.repository.AccountRepository;
import org.mercury_im.messenger.persistence.repository.ChatRepository;
import org.mercury_im.messenger.persistence.repository.EntityCapsRepository;
import org.mercury_im.messenger.persistence.repository.RosterRepository;
import org.mercury_im.messenger.thread_utils.ThreadUtils;
import javax.inject.Named;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
import io.reactivex.Scheduler;
import io.requery.Persistable;
import io.requery.reactivex.ReactiveEntityStore;
@Module
public class RequeryModule {
@Provides
@Singleton
public static AccountRepository provideAccountRepository(ReactiveEntityStore<Persistable> data,
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler) {
return new AccountRepository(data, ioScheduler, uiScheduler);
}
@Provides
@Singleton
public static ChatRepository provideChatRepository(ReactiveEntityStore<Persistable> data,
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler) {
return new ChatRepository(data, ioScheduler, uiScheduler);
}
@Provides
@Singleton
public static EntityCapsRepository provideCapsRepository(ReactiveEntityStore<Persistable> data,
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler) {
return new EntityCapsRepository(data, ioScheduler, uiScheduler);
}
@Provides
@Singleton
public static RosterRepository provideRosterRepository(ReactiveEntityStore<Persistable> data,
@Named(value = ThreadUtils.SCHEDULER_IO) Scheduler ioScheduler,
@Named(value = ThreadUtils.SCHEDULER_UI) Scheduler uiScheduler) {
return new RosterRepository(data, ioScheduler, uiScheduler);
}
}

View File

@ -0,0 +1,38 @@
package org.mercury_im.messenger.persistence.entity;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.converter.EntityBareJidConverter;
import io.requery.Column;
import io.requery.Convert;
import io.requery.Entity;
import io.requery.Generated;
import io.requery.Key;
import io.requery.Persistable;
import io.requery.Table;
@Table(name = "accounts")
@Entity
public abstract class AbstractAccountModel implements Persistable {
@Key @Generated
long id;
@Column(nullable = false)
@Convert(EntityBareJidConverter.class)
EntityBareJid jid;
@Column(nullable = false)
String password;
boolean enabled;
String rosterVersion;
@Override
public String toString() {
return "Account[" + id + ", " +
jid + ", " +
(enabled ? "enabled" : "disabled") + "]";
}
}

View File

@ -0,0 +1,23 @@
package org.mercury_im.messenger.persistence.entity;
import io.requery.Entity;
import io.requery.ForeignKey;
import io.requery.Generated;
import io.requery.Key;
import io.requery.OneToOne;
import io.requery.Persistable;
import io.requery.Table;
@Entity
@Table(name = "chats")
public abstract class AbstractChatModel implements Persistable {
@Key @Generated
long id;
@OneToOne
@ForeignKey
EntityModel peer;
boolean displayed;
}

View File

@ -0,0 +1,44 @@
package org.mercury_im.messenger.persistence.entity;
import org.mercury_im.messenger.persistence.converter.SubscriptionDirectionConverter;
import org.mercury_im.messenger.persistence.enums.SubscriptionDirection;
import io.requery.Convert;
import io.requery.Entity;
import io.requery.ForeignKey;
import io.requery.Generated;
import io.requery.Key;
import io.requery.OneToOne;
import io.requery.Persistable;
import io.requery.Table;
@Entity
@Table(name = "contacts")
public abstract class AbstractContactModel implements Persistable {
@Key @Generated
long id;
@OneToOne
@ForeignKey(referencedColumn = "id")
EntityModel entity;
String rostername;
@Convert(SubscriptionDirectionConverter.class)
SubscriptionDirection sub_direction;
boolean sub_pending;
boolean sub_approved;
@Override
public String toString() {
return "Contact[" + id + ", " +
rostername + ", " +
entity + ", " +
sub_direction + ", " +
(sub_pending ? "pending" : "not pending") + ", " +
(sub_approved ? "approved" : "not approved") + "]";
}
}

View File

@ -0,0 +1,19 @@
package org.mercury_im.messenger.persistence.entity;
import io.requery.Column;
import io.requery.Entity;
import io.requery.Key;
import io.requery.Persistable;
import io.requery.Table;
@Table(name = "entity_caps")
@Entity
public abstract class AbstractEntityCapsModel implements Persistable {
@Key
String nodeVer;
@Column(nullable = false)
String xml;
}

View File

@ -0,0 +1,35 @@
package org.mercury_im.messenger.persistence.entity;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.converter.EntityBareJidConverter;
import io.requery.Column;
import io.requery.Convert;
import io.requery.Entity;
import io.requery.Generated;
import io.requery.Key;
import io.requery.ManyToOne;
import io.requery.Persistable;
import io.requery.Table;
@Entity
@Table(name = "entities")
public abstract class AbstractEntityModel implements Persistable {
@Key @Generated
long id;
@ManyToOne
AccountModel account;
@Convert(EntityBareJidConverter.class)
@Column(nullable = false)
EntityBareJid jid;
@Override
public String toString() {
return "Entity[" + id + ", " +
jid + ", " +
account + "]";
}
}

View File

@ -0,0 +1,22 @@
package org.mercury_im.messenger.persistence.entity;
import io.requery.Entity;
import io.requery.ForeignKey;
import io.requery.Key;
import io.requery.OneToOne;
import io.requery.Persistable;
import io.requery.Table;
@Entity
@Table(name = "last_messages")
public abstract class AbstractLastChatMessageRelation implements Persistable {
@Key
@OneToOne
@ForeignKey
ChatModel chat;
@OneToOne
@ForeignKey
MessageModel message;
}

View File

@ -0,0 +1,22 @@
package org.mercury_im.messenger.persistence.entity;
import io.requery.Entity;
import io.requery.ForeignKey;
import io.requery.Key;
import io.requery.OneToOne;
import io.requery.Persistable;
import io.requery.Table;
@Entity
@Table(name = "last_read_messages")
public abstract class AbstractLastReadChatMessageRelation implements Persistable {
@Key
@OneToOne
@ForeignKey
ChatModel chat;
@OneToOne
@ForeignKey
MessageModel message;
}

View File

@ -0,0 +1,55 @@
package org.mercury_im.messenger.persistence.entity;
import org.jxmpp.jid.EntityBareJid;
import org.mercury_im.messenger.persistence.converter.EntityBareJidConverter;
import java.util.Date;
import io.requery.Column;
import io.requery.Convert;
import io.requery.Entity;
import io.requery.ForeignKey;
import io.requery.Generated;
import io.requery.Key;
import io.requery.ManyToOne;
import io.requery.Persistable;
import io.requery.Table;
@Entity
@Table(name = "messages")
public abstract class AbstractMessageModel implements Persistable {
@Key @Generated
long id;
@ForeignKey(referencedColumn = "id")
@ManyToOne
ChatModel chat;
String body;
@Column(name = "\"timestamp\"", nullable = false)
Date timestamp;
@Convert(EntityBareJidConverter.class)
@Column(nullable = false)
EntityBareJid sender;
@Convert(EntityBareJidConverter.class)
@Column(nullable = false)
EntityBareJid recipient;
boolean incoming;
String thread;
@Column(nullable = false)
String legacyId;
String originId;
String stanzaId;
@Convert(EntityBareJidConverter.class)
EntityBareJid stanzaIdBy;
}

View File

@ -0,0 +1,22 @@
package org.mercury_im.messenger.persistence.entity;
import org.mercury_im.messenger.persistence.enums.SaslCondition;
import io.requery.Entity;
import io.requery.Key;
import io.requery.ManyToOne;
import io.requery.Persistable;
import io.requery.Table;
@Entity
@Table(name = "sasl_auth")
public abstract class AbstractSaslAuthenticationResultModel implements Persistable {
@Key
@ManyToOne
AccountModel account;
SaslCondition saslCondition;
String descriptiveText;
}

View File

@ -0,0 +1,118 @@
package org.mercury_im.messenger.persistence.enums;
public enum SaslCondition {
// Success
/**
* The SASL handshake is considered successful.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-process-neg-success">rfc6120 $6.4.6: SASL Success</a>
*/
success,
// Failure
/**
* The receiving entity acknowledges that the authentication handshake has been aborted by the
* initiating entity; sent in reply to the <pre><abort/></pre> element.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-aborted">rfc6120 §6.5.1: aborted</a>
*/
aborted,
/**
* The account of the initiating entity has been temporarily disabled; sent in reply to an
* <pre><auth/></pre> element (with or without initial response data) or a
* <pre><response/></pre> element.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-account-disabled">rfc6120 §6.5.2: account-disabled</a>
*/
account_disabled,
/**
* The authentication failed because the initiating entity provided credentials that have
* expired; sent in reply to a <pre><response/></pre> element or an <pre><auth/></pre> element
* with initial response data.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-credentials-expired">rfc6120 §6.5.3: credentials-expired</a>
*/
credentials_expired,
/**
* The mechanism requested by the initiating entity cannot be used unless the confidentiality
* and integrity of the underlying stream are protected (typically via TLS);
* sent in reply to an <pre><auth/></pre> element (with or without initial response data).
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-encryption-required">rfc6120 §6.5.4: encryption-required</a>
*/
encryption_required,
/**
* The data provided by the initiating entity could not be processed because the base 64
* encoding is incorrect (e.g., because the encoding does not adhere to the definition in
* Section 4 of [BASE64]); sent in reply to a <pre><response/></pre> element or an
* <pre><auth/></pre> element with initial response data.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-incorrect-encoding">rfc6120 §6.5.5: incorrect-encoding</a>
*/
incorrect_encoding,
/**
* The authzid provided by the initiating entity is invalid, either because it is incorrectly
* formatted or because the initiating entity does not have permissions to authorize that ID;
* sent in reply to a <pre><response/></pre> element or an <pre><auth/></pre> element with
* initial response data.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-invalid-authzid">rfc6120 §6.5.6: invalid-authzid</a>
*/
invalid_authzid,
/**
* The initiating entity did not specify a mechanism, or requested a mechanism that is not
* supported by the receiving entity; sent in reply to an <pre><auth/></pre> element.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-invalid-mechanism">rfc6120 §6.5.7: invalid-mechanism</a>
*/
invalid_mechanism,
/**
* The request is malformed (e.g., the <pre><auth/></pre> element includes initial response
* data but the mechanism does not allow that, or the data sent violates the syntax for the
* specified SASL mechanism);
* sent in reply to an <pre><abort/></pre>, <pre><auth/></pre>, <pre><challenge/></pre>,
* or <pre><response/></pre> element.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-malformed-request">rfc6120 §6.5.8: malformed-request</a>
*/
malformed_request,
/**
* The mechanism requested by the initiating entity is weaker than server policy permits for
* that initiating entity; sent in reply to an <pre><auth/></pre> element (with or without
* initial response data).
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-mechanism-too-weak">rfc6120 §6.5.9: mechanism-too-weak</a>
*/
mechanism_too_weak,
/**
* The authentication failed because the initiating entity did not provide proper credentials,
* or because some generic authentication failure has occurred but the receiving entity does
* not wish to disclose specific information about the cause of the failure;
* sent in reply to a <pre><response/></pre> element or an <pre><auth/></pre> element with
* initial response data.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-not-authorized">rfc6120 §6.5.10: not-authorized</a>
*/
not_authorized,
/**
* The authentication failed because of a temporary error condition within the receiving entity,
* and it is advisable for the initiating entity to try again later;
* sent in reply to an <pre><auth/></pre> element or a <pre><response/></pre> element.
*
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#sasl-errors-temporary-auth-failure">rfc6120 §6.5.11: temporary-auth-failure</a>
*/
temporary_auth_failure
}

View File

@ -0,0 +1,9 @@
package org.mercury_im.messenger.persistence.enums;
public enum SubscriptionDirection {
none,
to,
from,
both,
remove
}

View File

@ -1,22 +0,0 @@
package org.mercury_im.messenger.persistence.model;
import java.util.Objects;
public abstract class AbstractAccountModel implements AccountModel {
@Override
public boolean equals(Object other) {
if (other == null) return false;
if (this == other) return true;
if (!(other instanceof AccountModel)) {
return false;
}
AccountModel o = (AccountModel) other;
return o.getId() == getId() &&
Objects.equals(o.getJid(), getJid()) &&
Objects.equals(o.getEnabled(), getEnabled()) &&
Objects.equals(o.getPassword(), getPassword()) &&
Objects.equals(o.getState(), getState());
}
}

View File

@ -1,74 +0,0 @@
package org.mercury_im.messenger.persistence.model;
import org.jxmpp.jid.EntityBareJid;
/**
* Interface representing an account database entity.
*/
public interface AccountModel {
/**
* Primary key.
*
* @return account id
*/
long getId();
/**
* Set the primary key.
*
* @param id account id
*/
void setId(long id);
/**
* Return the password of the XMPP account.
*
* @return password
*/
String getPassword();
/**
* Set the password of the XMPP account.
*
* @param password password
*/
void setPassword(String password);
/**
* Return the JID of the XMPP account.
*
* @return XMPP address
*/
EntityBareJid getJid();
/**
* Set the JID of the XMPP account.
*
* @param jid XMPP address
*/
void setJid(EntityBareJid jid);
/**
* Is the account enabled (should it be online).
*
* @return account enabled?
*/
boolean getEnabled();
/**
* Set whether or not the account is enabled and active.
*
* @param enabled enabled
*/
void setEnabled(boolean enabled);
/**
* Return the state of the connection.
*
* @return state
*/
String getState();
void setState(String state);
}

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