package com.beowulfe.hap.impl.connections; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.beowulfe.hap.characteristics.EventableCharacteristic; import com.beowulfe.hap.impl.http.HomekitClientConnection; import com.beowulfe.hap.impl.http.HttpResponse; import com.beowulfe.hap.impl.json.EventController; public class SubscriptionManager { private final static Logger LOGGER = LoggerFactory.getLogger(SubscriptionManager.class); private final ConcurrentMap<EventableCharacteristic, Set<HomekitClientConnection>> subscriptions = new ConcurrentHashMap<>(); private final ConcurrentMap<HomekitClientConnection, Set<EventableCharacteristic>> reverse = new ConcurrentHashMap<>(); public synchronized void addSubscription(int aid, int iid, EventableCharacteristic characteristic, HomekitClientConnection connection) { synchronized(this) { if (!subscriptions.containsKey(characteristic)) { subscriptions.putIfAbsent(characteristic, newSet()); } subscriptions.get(characteristic).add(connection); if (subscriptions.get(characteristic).size() == 1) { characteristic.subscribe(() -> { publish(aid, iid, characteristic); }); } if (!reverse.containsKey(connection)) { reverse.putIfAbsent(connection, newSet()); } reverse.get(connection).add(characteristic); LOGGER.info("Added subscription to "+characteristic.getClass()+" for "+connection.hashCode()); } try { connection.outOfBand(new EventController().getMessage(aid, iid, characteristic)); } catch (Exception e) { LOGGER.error("Could not send initial state in response to subscribe event", e); } } public synchronized void removeSubscription(EventableCharacteristic characteristic, HomekitClientConnection connection) { Set<HomekitClientConnection> subscriptions = this.subscriptions.get(characteristic); if (subscriptions != null) { subscriptions.remove(connection); if (subscriptions.size() == 0) { characteristic.unsubscribe(); } } Set<EventableCharacteristic> reverse = this.reverse.get(connection); if (reverse != null) { reverse.remove(characteristic); } LOGGER.info("Removed subscription to "+characteristic.getClass()+" for "+connection.hashCode()); } public synchronized void removeConnection(HomekitClientConnection connection) { Set<EventableCharacteristic> characteristics = reverse.remove(connection); if (characteristics != null) { for (EventableCharacteristic characteristic: characteristics) { Set<HomekitClientConnection> characteristicSubscriptions = subscriptions.get(characteristic); characteristicSubscriptions.remove(connection); if (characteristicSubscriptions.isEmpty()) { characteristic.unsubscribe(); } } } } private <T> Set<T> newSet() { return Collections.newSetFromMap(new ConcurrentHashMap<T, Boolean>()); } public void publish(int accessoryId, int iid, EventableCharacteristic changed) { try { HttpResponse message = new EventController().getMessage(accessoryId, iid, changed); LOGGER.info("Publishing changes for "+accessoryId); for (HomekitClientConnection connection: subscriptions.get(changed)) { connection.outOfBand(message); } } catch (Exception e) { LOGGER.error("Failed to create new event message", e); } } }