package cc.blynk.server.hardware.handlers.hardware; import cc.blynk.server.Holder; 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.notifications.push.GCMWrapper; import cc.blynk.utils.properties.Placeholders; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.timeout.IdleStateEvent; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.util.concurrent.TimeUnit; import static cc.blynk.server.internal.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; private final String pushNotificationBody; public HardwareChannelStateHandler(Holder holder) { this.sessionDao = holder.sessionDao; this.gcmWrapper = holder.gcmWrapper; this.pushNotificationBody = holder.textHolder.pushNotificationBody; } @Override public void channelInactive(ChannelHandlerContext ctx) { var hardwareChannel = ctx.channel(); var state = getHardState(hardwareChannel); if (state != null) { var session = sessionDao.get(state.userKey); if (session != null) { var device = state.device; log.trace("Hardware channel disconnect for {}, dashId {}, deviceId {}, token {}.", state.userKey, state.dash.id, device.id, device.token); sentOfflineMessage(ctx, session, state.dash, device); } } } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { if (evt instanceof IdleStateEvent) { log.trace("State handler. Hardware timeout disconnect. Event : {}. Closing.", ((IdleStateEvent) evt).state()); ctx.close(); } else { ctx.fireUserEventTriggered(evt); } } private void sentOfflineMessage(ChannelHandlerContext ctx, Session session, DashBoard dashBoard, Device device) { //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(dashBoard.id, device.id); if (!isHardwareConnected) { log.trace("Changing device status. DeviceId {}, dashId {}", device.id, dashBoard.id); device.disconnected(); } if (!dashBoard.isActive) { return; } Notification notification = dashBoard.getNotificationWidget(); if (notification != null && notification.notifyWhenOffline) { sendPushNotification(ctx, session, notification, dashBoard, device); } else if (!dashBoard.isNotificationsOff) { session.sendOfflineMessageToApps(dashBoard.id, device.id); } } private void sendPushNotification(ChannelHandlerContext ctx, Session session, Notification notification, DashBoard dash, Device device) { var deviceName = ((device == null || device.name == null) ? "device" : device.name); var message = pushNotificationBody.replace(Placeholders.DEVICE_NAME, deviceName); if (notification.notifyWhenOfflineIgnorePeriod == 0 || device == null) { if (!dash.isNotificationsOff && device != null) { session.sendOfflineMessageToApps(dash.id, device.id); } notification.push(gcmWrapper, message, dash.id ); } else { //delayed notification //https://github.com/blynkkk/blynk-server/issues/493 ctx.executor().schedule(new DelayedPush(session, device, notification, message, dash), notification.notifyWhenOfflineIgnorePeriod, TimeUnit.MILLISECONDS); } } private final class DelayedPush implements Runnable { private final Session session; private final Device device; private final Notification notification; private final String message; private final DashBoard dash; DelayedPush(Session session, Device device, Notification notification, String message, DashBoard dash) { this.session = session; this.device = device; this.notification = notification; this.message = message; this.dash = dash; } @Override public void run() { if (device.status == Status.OFFLINE) { if (!dash.isNotificationsOff) { session.sendOfflineMessageToApps(dash.id, device.id); } long now = System.currentTimeMillis(); if (now - device.disconnectTime >= notification.notifyWhenOfflineIgnorePeriod) { notification.push(gcmWrapper, message, dash.id ); } } } } }