package cc.blynk.server.hardware.handlers.hardware;
import cc.blynk.server.core.dao.SessionDao;
import cc.blynk.server.core.model.DashBoard;
import cc.blynk.server.core.model.auth.Session;
import cc.blynk.server.core.model.device.Device;
import cc.blynk.server.core.model.device.Status;
import cc.blynk.server.core.model.widgets.notifications.Notification;
import cc.blynk.server.core.session.HardwareStateHolder;
import cc.blynk.server.notifications.push.GCMWrapper;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.ReadTimeoutException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.concurrent.TimeUnit;
import static cc.blynk.utils.StateHolderUtil.getHardState;
/**
* The Blynk Project.
* Created by Dmitriy Dumanskiy.
* Created on 2/20/2015.
*
* Removes channel from session in case it became inactive (closed from client side).
*/
@ChannelHandler.Sharable
public class HardwareChannelStateHandler extends ChannelInboundHandlerAdapter {
private static final Logger log = LogManager.getLogger(HardwareChannelStateHandler.class);
private final SessionDao sessionDao;
private final GCMWrapper gcmWrapper;
public HardwareChannelStateHandler(SessionDao sessionDao, GCMWrapper gcmWrapper) {
this.sessionDao = sessionDao;
this.gcmWrapper = gcmWrapper;
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
HardwareStateHolder state = getHardState(ctx.channel());
if (state != null) {
Session session = sessionDao.userSession.get(state.userKey);
if (session != null) {
session.removeHardChannel(ctx.channel());
log.trace("Hardware channel disconnect.");
sentOfflineMessage(ctx, session, state);
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (cause instanceof ReadTimeoutException) {
log.trace("Hardware timeout disconnect.");
} else {
super.exceptionCaught(ctx, cause);
}
}
private void sentOfflineMessage(ChannelHandlerContext ctx, Session session, HardwareStateHolder state) {
DashBoard dashBoard = state.user.profile.getDashByIdOrThrow(state.dashId);
Device device = dashBoard.getDeviceById(state.deviceId);
//this is special case.
//in case hardware quickly reconnects we do not mark it as disconnected
//as it is already online after quick disconnect.
//https://github.com/blynkkk/blynk-server/issues/403
boolean isHardwareConnected = session.isHardwareConnected(state.dashId, state.deviceId);
if (device != null && !isHardwareConnected) {
log.trace("Disconnected device id {}, dash id {}", state.deviceId, state.dashId);
device.disconnected();
}
if (!dashBoard.isActive) {
return;
}
final Notification notification = dashBoard.getWidgetByType(Notification.class);
if (notification != null && notification.notifyWhenOffline) {
sendPushNotification(ctx, dashBoard, notification, state.dashId, device);
} else {
session.sendOfflineMessageToApps(state.dashId);
}
}
private void sendPushNotification(ChannelHandlerContext ctx, DashBoard dashBoard, Notification notification, int dashId, Device device) {
final String dashName = dashBoard.name == null ? "" : dashBoard.name;
final String deviceName = ((device == null || device.name == null) ? "device" : device.name);
String message = "Your " + deviceName + " went offline. \"" + dashName + "\" project is disconnected.";
if (notification.notifyWhenOfflineIgnorePeriod == 0 || device == null) {
notification.push(gcmWrapper,
message,
dashId
);
} else {
//delayed notification
//https://github.com/blynkkk/blynk-server/issues/493
ctx.executor().schedule(new DelayedPush(device, notification, message, dashId),
notification.notifyWhenOfflineIgnorePeriod, TimeUnit.MILLISECONDS);
}
}
private final class DelayedPush implements Runnable {
private final Device device;
private final Notification notification;
private final String message;
private final int dashId;
public DelayedPush(Device device, Notification notification, String message, int dashId) {
this.device = device;
this.notification = notification;
this.message = message;
this.dashId = dashId;
}
@Override
public void run() {
final long now = System.currentTimeMillis();
if (device.status == Status.OFFLINE &&
now - device.disconnectTime > notification.notifyWhenOfflineIgnorePeriod) {
notification.push(gcmWrapper,
message,
dashId
);
}
}
}
}