/*
* Copyright (C) 2015 Actor LLC. <https://actor.im>
*/
package im.actor.core.modules.presence;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import im.actor.core.api.ApiGroupOutPeer;
import im.actor.core.api.ApiUserOutPeer;
import im.actor.core.api.rpc.RequestSubscribeToGroupOnline;
import im.actor.core.api.rpc.RequestSubscribeToOnline;
import im.actor.core.api.rpc.ResponseVoid;
import im.actor.core.entity.Group;
import im.actor.core.entity.GroupType;
import im.actor.core.entity.Peer;
import im.actor.core.entity.PeerType;
import im.actor.core.entity.User;
import im.actor.core.modules.ModuleContext;
import im.actor.core.events.NewSessionCreated;
import im.actor.core.events.PeerChatOpened;
import im.actor.core.events.PeerInfoOpened;
import im.actor.core.events.UserVisible;
import im.actor.core.modules.ModuleActor;
import im.actor.core.viewmodel.GroupVM;
import im.actor.core.viewmodel.UserPresence;
import im.actor.core.viewmodel.UserVM;
import im.actor.runtime.Log;
import im.actor.runtime.actors.ActorCreator;
import im.actor.runtime.actors.ActorRef;
import im.actor.runtime.actors.ActorSystem;
import im.actor.runtime.actors.ActorCancellable;
import im.actor.runtime.actors.Props;
import im.actor.runtime.annotations.Verified;
import im.actor.runtime.eventbus.BusSubscriber;
import im.actor.runtime.eventbus.Event;
import im.actor.runtime.function.Consumer;
import im.actor.runtime.promise.Promise;
import im.actor.runtime.promise.PromisesArray;
@Verified
public class PresenceActor extends ModuleActor implements BusSubscriber {
public static ActorRef create(final ModuleContext messenger) {
return ActorSystem.system().actorOf("actor/presence", () -> new PresenceActor(messenger));
}
private static final int ONLINE_TIMEOUT = 5 * 60 * 1000;
// private static final String TAG = "PresenceActor";
private HashMap<Integer, Long> lastUidState = new HashMap<>();
private HashMap<Integer, Long> lastGidState = new HashMap<>();
private HashMap<Integer, ActorCancellable> uidCancellables = new HashMap<>();
private HashSet<Integer> uids = new HashSet<>();
private HashSet<Integer> gids = new HashSet<>();
private boolean isRequesting = false;
private ArrayList<Peer> pendingPeers = new ArrayList<>();
public PresenceActor(ModuleContext messenger) {
super(messenger);
}
@Override
public void preStart() {
context().getEvents().subscribe(this, NewSessionCreated.EVENT);
context().getEvents().subscribe(this, PeerChatOpened.EVENT);
context().getEvents().subscribe(this, PeerInfoOpened.EVENT);
context().getEvents().subscribe(this, UserVisible.EVENT);
}
@Verified
private void onUserOnline(int uid, long updateDate) {
// Log.d(TAG, "onUserOnline #" + uid + " at " + updateDate);
if (lastUidState.containsKey(uid) && lastUidState.get(uid) >= updateDate) {
// Log.d(TAG, "onUserOnline:ignored - too old");
return;
}
lastUidState.put(uid, updateDate);
// Log.d(TAG, "onUserOnline:updated");
UserVM vm = getUserVM(uid);
if (vm != null) {
vm.getPresence().change(new UserPresence(UserPresence.State.ONLINE));
}
// Updating timeout
if (uidCancellables.containsKey(uid)) {
uidCancellables.remove(uid).cancel();
}
uidCancellables.put(uid, schedule(new OnlineUserTimeout(uid, (int) ((updateDate + ONLINE_TIMEOUT) / 1000L),
updateDate + ONLINE_TIMEOUT), ONLINE_TIMEOUT));
}
@Verified
private void onUserOffline(int uid, long updateDate) {
// Log.d(TAG, "onUserOffline #" + uid + " at " + updateDate);
if (lastUidState.containsKey(uid) && lastUidState.get(uid) >= updateDate) {
// Log.d(TAG, "onUserOffline:ignored - too old");
return;
}
lastUidState.put(uid, updateDate);
// Log.d(TAG, "onUserOffline:updated");
UserVM vm = getUserVM(uid);
if (vm != null) {
vm.getPresence().change(new UserPresence(UserPresence.State.OFFLINE));
}
// Cancel timeout
if (uidCancellables.containsKey(uid)) {
uidCancellables.remove(uid).cancel();
}
}
@Verified
private void onUserLastSeen(int uid, int date, long updateDate) {
// Log.d(TAG, "onUserLastSeen #" + uid + " at " + date + " at " + updateDate);
if (lastUidState.containsKey(uid) && lastUidState.get(uid) >= updateDate) {
// Log.d(TAG, "onUserLastSeen:ignored - too old");
return;
}
lastUidState.put(uid, updateDate);
// Log.d(TAG, "onUserLastSeen:updated");
UserVM vm = getUserVM(uid);
if (vm != null) {
vm.getPresence().change(new UserPresence(UserPresence.State.OFFLINE, date));
}
// Cancel timeout
if (uidCancellables.containsKey(uid)) {
uidCancellables.remove(uid).cancel();
}
}
private void onUserGoesOffline(int uid, int date, long updateDate) {
// Log.d(TAG, "onUserGoesOffline #" + uid + " at " + date + " at " + updateDate);
if (lastUidState.containsKey(uid) && lastUidState.get(uid) >= updateDate) {
// Log.d(TAG, "onUserGoesOffline:ignored - too old");
return;
}
lastUidState.put(uid, updateDate);
// Log.d(TAG, "onUserGoesOffline:updated");
UserVM vm = getUserVM(uid);
if (vm != null) {
vm.getPresence().change(new UserPresence(UserPresence.State.OFFLINE, date));
}
// Cancel timeout
if (uidCancellables.containsKey(uid)) {
uidCancellables.remove(uid).cancel();
}
}
@Verified
private void onGroupOnline(int gid, int count, long updateDate) {
// Log.d(TAG, "onGroupOnline #" + gid + " " + count + " at " + updateDate);
if (lastGidState.containsKey(gid) && lastGidState.get(gid) >= updateDate) {
// Log.d(TAG, "onGroupOnline:ignored - too old");
return;
}
lastGidState.put(gid, updateDate);
// Log.d(TAG, "onGroupOnline:updated");
GroupVM vm = getGroupVM(gid);
if (vm != null) {
vm.getPresence().change(count);
}
}
@Verified
private void subscribe(Peer peer) {
// Log.d(TAG, "subscribe:" + peer);
if (peer.getPeerType() == PeerType.PRIVATE) {
// Already subscribed
if (uids.contains(peer.getPeerId())) {
return;
}
User user = getUser(peer.getPeerId());
if (user == null) {
return;
}
// Subscribing to user online sates
uids.add(user.getUid());
} else if (peer.getPeerType() == PeerType.GROUP) {
// Already subscribed
if (gids.contains(peer.getPeerId())) {
return;
}
Group group = getGroup(peer.getPeerId());
if (group == null) {
return;
}
// Ignore subscription to channels
if (group.getGroupType() == GroupType.CHANNEL) {
return;
}
// Subscribing to group online sates
gids.add(peer.getPeerId());
} else {
return;
}
// Adding Pending Peer
if (pendingPeers.contains(peer)) {
return;
}
pendingPeers.add(peer);
onCheckQueue();
}
@Verified
private void onNewSessionCreated() {
// Resubscribing for online states of users
for (int uid : uids) {
Peer p = Peer.user(uid);
if (!pendingPeers.contains(p)) {
pendingPeers.add(p);
}
}
// Resubscribing for online states of groups
for (int gid : gids) {
Peer p = Peer.group(gid);
if (!pendingPeers.contains(p)) {
pendingPeers.add(p);
}
}
onCheckQueue();
}
private void onCheckQueue() {
if (isRequesting) {
return;
}
if (pendingPeers.size() == 0) {
return;
}
ArrayList<Peer> destPeers = new ArrayList<>(pendingPeers);
pendingPeers.clear();
ArrayList<ApiUserOutPeer> outUserPeers = new ArrayList<>();
ArrayList<ApiGroupOutPeer> outGroupPeers = new ArrayList<>();
for (Peer p : destPeers) {
if (p.getPeerType() == PeerType.GROUP) {
Group g = getGroup(p.getPeerId());
if (g != null) {
outGroupPeers.add(new ApiGroupOutPeer(p.getPeerId(), g.getAccessHash()));
}
} else if (p.getPeerType() == PeerType.PRIVATE) {
User u = getUser(p.getPeerId());
if (u != null) {
outUserPeers.add(new ApiUserOutPeer(p.getPeerId(), u.getAccessHash()));
}
}
}
ArrayList<Promise<ResponseVoid>> requests = new ArrayList<>();
if (outUserPeers.size() > 0) {
requests.add(api(new RequestSubscribeToOnline(outUserPeers)));
}
if (outGroupPeers.size() > 0) {
requests.add(api(new RequestSubscribeToGroupOnline(outGroupPeers)));
}
if (requests.size() > 0) {
isRequesting = true;
PromisesArray.ofPromises(requests).zip().then(responseVoids -> {
isRequesting = false;
onCheckQueue();
}).failure(e -> {
isRequesting = false;
onCheckQueue();
});
}
}
// Messages
@Override
public void onReceive(Object message) {
if (message instanceof UserOnline) {
UserOnline online = (UserOnline) message;
onUserOnline(online.getUid(), online.getUpdateDate());
} else if (message instanceof UserOffline) {
UserOffline offline = (UserOffline) message;
onUserOffline(offline.getUid(), offline.getUpdateDate());
} else if (message instanceof UserLastSeen) {
UserLastSeen lastSeen = (UserLastSeen) message;
onUserLastSeen(lastSeen.getUid(), lastSeen.getDate(), lastSeen.getUpdateDate());
} else if (message instanceof GroupOnline) {
GroupOnline groupOnline = (GroupOnline) message;
onGroupOnline(groupOnline.getGid(), groupOnline.getCount(), groupOnline.getUpdateDate());
} else if (message instanceof Subscribe) {
subscribe(((Subscribe) message).getPeer());
} else if (message instanceof SessionCreated) {
onNewSessionCreated();
} else if (message instanceof OnlineUserTimeout) {
OnlineUserTimeout timeout = (OnlineUserTimeout) message;
onUserGoesOffline(timeout.getUid(), timeout.getDate(), timeout.getUpdateDate());
} else {
super.onReceive(message);
}
}
@Override
public void onBusEvent(Event event) {
if (event instanceof NewSessionCreated) {
self().send(new SessionCreated());
} else if (event instanceof PeerChatOpened) {
self().send(new Subscribe(((PeerChatOpened) event).getPeer()));
} else if (event instanceof PeerInfoOpened) {
self().send(new Subscribe(((PeerInfoOpened) event).getPeer()));
} else if (event instanceof UserVisible) {
self().send(new Subscribe(Peer.user(((UserVisible) event).getUid())));
}
}
public static class UserOnline {
private int uid;
private long updateDate;
public UserOnline(int uid, long updateDate) {
this.uid = uid;
this.updateDate = updateDate;
}
public int getUid() {
return uid;
}
public long getUpdateDate() {
return updateDate;
}
}
public static class UserOffline {
private int uid;
private long updateDate;
public UserOffline(int uid, long updateDate) {
this.uid = uid;
this.updateDate = updateDate;
}
public int getUid() {
return uid;
}
public long getUpdateDate() {
return updateDate;
}
}
public static class UserLastSeen {
private int uid;
private int date;
private long updateDate;
public UserLastSeen(int uid, int date, long updateDate) {
this.uid = uid;
this.date = date;
this.updateDate = updateDate;
}
public int getUid() {
return uid;
}
public int getDate() {
return date;
}
public long getUpdateDate() {
return updateDate;
}
}
public static class GroupOnline {
private int gid;
private int count;
private long updateDate;
public GroupOnline(int gid, int count, long updateDate) {
this.gid = gid;
this.count = count;
this.updateDate = updateDate;
}
public int getGid() {
return gid;
}
public int getCount() {
return count;
}
public long getUpdateDate() {
return updateDate;
}
}
private static class OnlineUserTimeout {
private int uid;
private int date;
private long updateDate;
public OnlineUserTimeout(int uid, int date, long updateDate) {
this.uid = uid;
this.date = date;
this.updateDate = updateDate;
}
public int getUid() {
return uid;
}
public int getDate() {
return date;
}
public long getUpdateDate() {
return updateDate;
}
}
public static class Subscribe {
private Peer peer;
public Subscribe(Peer peer) {
this.peer = peer;
}
public Peer getPeer() {
return peer;
}
}
public static class SessionCreated {
}
}