/*
* Copyright (C) 2014-2015 Actor LLC. <https://actor.im>
*/
package im.actor.core.modules.groups;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import im.actor.core.api.ApiAdminSettings;
import im.actor.core.api.ApiGroupOutPeer;
import im.actor.core.api.ApiGroupType;
import im.actor.core.api.ApiMember;
import im.actor.core.api.ApiOutPeer;
import im.actor.core.api.ApiPeerType;
import im.actor.core.api.ApiUserOutPeer;
import im.actor.core.api.rpc.RequestCreateGroup;
import im.actor.core.api.rpc.RequestDeleteGroup;
import im.actor.core.api.rpc.RequestDismissUserAdmin;
import im.actor.core.api.rpc.RequestEditGroupAbout;
import im.actor.core.api.rpc.RequestEditGroupShortName;
import im.actor.core.api.rpc.RequestEditGroupTitle;
import im.actor.core.api.rpc.RequestEditGroupTopic;
import im.actor.core.api.rpc.RequestGetGroupInviteUrl;
import im.actor.core.api.rpc.RequestGetIntegrationToken;
import im.actor.core.api.rpc.RequestInviteUser;
import im.actor.core.api.rpc.RequestJoinGroup;
import im.actor.core.api.rpc.RequestJoinGroupByPeer;
import im.actor.core.api.rpc.RequestKickUser;
import im.actor.core.api.rpc.RequestLeaveAndDelete;
import im.actor.core.api.rpc.RequestLeaveGroup;
import im.actor.core.api.rpc.RequestLoadAdminSettings;
import im.actor.core.api.rpc.RequestLoadMembers;
import im.actor.core.api.rpc.RequestMakeUserAdminObsolete;
import im.actor.core.api.rpc.RequestRevokeIntegrationToken;
import im.actor.core.api.rpc.RequestRevokeInviteUrl;
import im.actor.core.api.rpc.RequestSaveAdminSettings;
import im.actor.core.api.rpc.RequestShareHistory;
import im.actor.core.api.rpc.RequestTransferOwnership;
import im.actor.core.api.rpc.ResponseIntegrationToken;
import im.actor.core.api.rpc.ResponseInviteUrl;
import im.actor.core.api.rpc.ResponseVoid;
import im.actor.core.entity.Group;
import im.actor.core.entity.GroupMember;
import im.actor.core.entity.GroupMembersSlice;
import im.actor.core.entity.GroupPermissions;
import im.actor.core.entity.Peer;
import im.actor.core.entity.PeerType;
import im.actor.core.entity.User;
import im.actor.core.events.PeerChatOpened;
import im.actor.core.events.PeerInfoOpened;
import im.actor.core.modules.AbsModule;
import im.actor.core.modules.ModuleContext;
import im.actor.core.modules.api.ApiSupportConfiguration;
import im.actor.core.modules.groups.router.GroupRouterInt;
import im.actor.core.modules.profile.avatar.GroupAvatarChangeActor;
import im.actor.core.util.RandomUtils;
import im.actor.core.viewmodel.GroupAvatarVM;
import im.actor.core.viewmodel.GroupVM;
import im.actor.runtime.Storage;
import im.actor.runtime.actors.ActorRef;
import im.actor.runtime.actors.messages.Void;
import im.actor.runtime.eventbus.BusSubscriber;
import im.actor.runtime.eventbus.Event;
import im.actor.runtime.function.Function;
import im.actor.runtime.mvvm.MVVMCollection;
import im.actor.runtime.promise.Promise;
import im.actor.runtime.promise.Promises;
import im.actor.runtime.storage.KeyValueEngine;
import static im.actor.runtime.actors.ActorSystem.system;
public class GroupsModule extends AbsModule implements BusSubscriber {
// Workaround for j2objc bug
private static final Void DUMB = null;
private static final ResponseVoid DUMB2 = null;
private final KeyValueEngine<Group> groups;
private final MVVMCollection<Group, GroupVM> collection;
private final HashMap<Integer, GroupAvatarVM> avatarVMs;
private final ActorRef avatarChangeActor;
private final GroupRouterInt groupRouterInt;
public GroupsModule(final ModuleContext context) {
super(context);
collection = Storage.createKeyValue(STORAGE_GROUPS, GroupVM.CREATOR, Group.CREATOR);
groups = collection.getEngine();
groupRouterInt = new GroupRouterInt(context);
avatarVMs = new HashMap<>();
avatarChangeActor = system().actorOf("actor/avatar/group", () -> new GroupAvatarChangeActor(context));
}
public void run() {
context().getEvents().subscribe(this, PeerChatOpened.EVENT);
context().getEvents().subscribe(this, PeerInfoOpened.EVENT);
}
//
// Storage
//
public KeyValueEngine<Group> getGroups() {
return groups;
}
public GroupAvatarVM getAvatarVM(int gid) {
synchronized (avatarVMs) {
if (!avatarVMs.containsKey(gid)) {
avatarVMs.put(gid, new GroupAvatarVM(gid));
}
return avatarVMs.get(gid);
}
}
public MVVMCollection<Group, GroupVM> getGroupsCollection() {
return collection;
}
public GroupRouterInt getRouter() {
return groupRouterInt;
}
//
// Actions
//
public Promise<Integer> createGroup(String title, String avatarDescriptor, int[] uids) {
return createGroup(title, avatarDescriptor, uids, ApiGroupType.GROUP);
}
public Promise<Integer> createChannel(String title, String avatarDescriptor) {
return createGroup(title, avatarDescriptor, new int[0], ApiGroupType.CHANNEL);
}
private Promise<Integer> createGroup(String title, String avatarDescriptor, int[] uids,
ApiGroupType groupType) {
long rid = RandomUtils.nextRid();
return Promise.success(uids)
.map((Function<int[], List<ApiUserOutPeer>>) ints -> {
ArrayList<ApiUserOutPeer> peers = new ArrayList<>();
for (int u : uids) {
User user = users().getValue(u);
if (user != null) {
peers.add(new ApiUserOutPeer(u, user.getAccessHash()));
}
}
return peers;
})
.flatMap(apiUserOutPeers ->
api(new RequestCreateGroup(rid, title, apiUserOutPeers,
groupType, ApiSupportConfiguration.OPTIMIZATIONS)))
.chain(r -> updates().applyRelatedData(r.getUsers(), r.getGroup()))
.chain(r -> updates().waitForUpdate(r.getSeq()))
.map(r -> r.getGroup().getId())
.then(integer -> {
if (avatarDescriptor != null) {
changeAvatar(integer, avatarDescriptor);
}
});
}
public Promise<Void> addMember(final int gid, final int uid) {
final long rid = RandomUtils.nextRid();
return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid))
.flatMap(groupUserTuple2 ->
api(new RequestInviteUser(
new ApiGroupOutPeer(gid, groupUserTuple2.getT1().getAccessHash()),
rid,
new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash()),
ApiSupportConfiguration.OPTIMIZATIONS)))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> kickMember(int gid, int uid) {
final long rid = RandomUtils.nextRid();
return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid))
.flatMap(groupUserTuple2 ->
api(new RequestKickUser(
new ApiGroupOutPeer(gid, groupUserTuple2.getT1().getAccessHash()),
rid,
new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash()),
ApiSupportConfiguration.OPTIMIZATIONS)))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> leaveGroup(int gid) {
final long rid = RandomUtils.nextRid();
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestLeaveGroup(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()),
rid,
ApiSupportConfiguration.OPTIMIZATIONS)))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> leaveAndDeleteGroup(int gid) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestLeaveAndDelete(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()))))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> deleteGroup(int gid) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestDeleteGroup(new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()))))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> shareHistory(int gid) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestShareHistory(new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()))))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> makeAdmin(final int gid, final int uid) {
return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid))
.flatMap(groupUserTuple2 -> api(new RequestMakeUserAdminObsolete(
new ApiGroupOutPeer(gid, groupUserTuple2.getT1().getAccessHash()),
new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash()))))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> revokeAdmin(final int gid, final int uid) {
return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid))
.flatMap(groupUserTuple2 -> api(new RequestDismissUserAdmin(
new ApiGroupOutPeer(gid, groupUserTuple2.getT1().getAccessHash()),
new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash()))))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> transferOwnership(final int gid, final int uid) {
return Promises.tuple(getGroups().getValueAsync(gid), users().getValueAsync(uid))
.flatMap(groupUserTuple2 -> api(new RequestTransferOwnership(
new ApiGroupOutPeer(gid, groupUserTuple2.getT1().getAccessHash()),
new ApiUserOutPeer(uid, groupUserTuple2.getT2().getAccessHash()))))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> editTitle(final int gid, final String name) {
final long rid = RandomUtils.nextRid();
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestEditGroupTitle(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()),
rid, name,
ApiSupportConfiguration.OPTIMIZATIONS)))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> editTheme(final int gid, final String theme) {
final long rid = RandomUtils.nextRid();
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestEditGroupTopic(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()),
rid, theme,
ApiSupportConfiguration.OPTIMIZATIONS)))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> editAbout(final int gid, final String about) {
final long rid = RandomUtils.nextRid();
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestEditGroupAbout(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()),
rid, about, ApiSupportConfiguration.OPTIMIZATIONS)))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<Void> editShortName(final int gid, final String shortName) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestEditGroupShortName(new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()),
shortName)))
.flatMap(r -> updates().waitForUpdate(r.getSeq()));
}
public Promise<GroupPermissions> loadAdminSettings(int gid) {
return getGroups().getValueAsync(gid)
.flatMap(group -> api(new RequestLoadAdminSettings(new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()))))
.map(r -> new GroupPermissions(r.getSettings()));
}
public Promise<Void> saveAdminSettings(int gid, GroupPermissions adminSettings) {
return getGroups().getValueAsync(gid)
.flatMap(group -> api(new RequestSaveAdminSettings(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()),
adminSettings.getApiSettings())))
.map(r -> null);
}
public Promise<GroupMembersSlice> loadMembers(int gid, int limit, byte[] next) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestLoadMembers(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()),
limit, next)))
.chain(r -> updates().loadRequiredPeers(r.getUsers(), new ArrayList<>()))
.map(r -> {
ArrayList<GroupMember> members = new ArrayList<>();
for (ApiMember p : r.getMembers()) {
boolean isAdmin = p.isAdmin() != null ? p.isAdmin() : false;
members.add(new GroupMember(p.getUid(),
p.getInviterUid(), p.getInviterUid(), isAdmin));
}
return new GroupMembersSlice(members, r.getNext());
});
}
public void changeAvatar(int gid, String descriptor) {
avatarChangeActor.send(new GroupAvatarChangeActor.ChangeAvatar(gid, descriptor));
}
public void removeAvatar(int gid) {
avatarChangeActor.send(new GroupAvatarChangeActor.RemoveAvatar(gid));
}
//
// Join
//
public Promise<String> requestInviteLink(final int gid) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestGetGroupInviteUrl(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()))))
.map(ResponseInviteUrl::getUrl);
}
public Promise<String> requestRevokeLink(final int gid) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestRevokeInviteUrl(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()))))
.map(ResponseInviteUrl::getUrl);
}
public Promise<Integer> joinGroupByToken(final String token) {
return api(new RequestJoinGroup(token, ApiSupportConfiguration.OPTIMIZATIONS))
.chain(responseJoinGroup -> updates().loadRequiredPeers(responseJoinGroup.getUserPeers(), new ArrayList<>()))
.chain(r -> updates().waitForUpdate(r.getSeq()))
.map(responseJoinGroup -> responseJoinGroup.getGroup().getId());
}
public Promise<Void> joinGroup(int gid) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestJoinGroupByPeer(
new ApiGroupOutPeer(group.getGroupId(), group.getAccessHash()))))
.chain(r -> updates().waitForUpdate(r.getSeq()))
.map(r -> null);
}
//
// Integration Token
//
public Promise<String> requestIntegrationToken(final int gid) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestGetIntegrationToken(
new ApiOutPeer(
ApiPeerType.GROUP,
group.getGroupId(),
group.getAccessHash()))))
.map(ResponseIntegrationToken::getUrl);
}
public Promise<String> revokeIntegrationToken(final int gid) {
return getGroups().getValueAsync(gid)
.flatMap(group ->
api(new RequestRevokeIntegrationToken(
new ApiOutPeer(
ApiPeerType.GROUP,
group.getGroupId(),
group.getAccessHash()))))
.map(ResponseIntegrationToken::getUrl);
}
public void resetModule() {
groups.clear();
}
@Override
public void onBusEvent(Event event) {
if (event instanceof PeerChatOpened) {
Peer peer = ((PeerChatOpened) event).getPeer();
if (peer.getPeerType() == PeerType.GROUP) {
getRouter().onFullGroupNeeded(peer.getPeerId());
}
} else if (event instanceof PeerInfoOpened) {
Peer peer = ((PeerInfoOpened) event).getPeer();
if (peer.getPeerType() == PeerType.GROUP) {
getRouter().onFullGroupNeeded(peer.getPeerId());
}
}
}
}