Skip to content

Commit 0e4db0b

Browse files
committed
keep subscriptions up to date when accessories are replaced
but the connection is _not_ torn down also add the ability to modify accessories in a batch without having to regenerate all AIDs and IIDs (and other temporary data structures) at each intermediate step. You must use this mechanism if you plan on replacing an existing accessory with an equivalent one, but want to keep subscriptions intact.
1 parent de16264 commit 0e4db0b

File tree

6 files changed

+213
-84
lines changed

6 files changed

+213
-84
lines changed

src/main/java/io/github/hapjava/characteristics/Characteristic.java

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
* @author Andy Lintner
1919
*/
2020
public interface Characteristic {
21+
/** @return The UUID type for this characteristic. */
22+
String getType();
2123

2224
/**
2325
* Adds an attribute to the passed JsonObjectBuilder named "value" with the current value of the

src/main/java/io/github/hapjava/characteristics/impl/base/BaseCharacteristic.java

+6
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ public BaseCharacteristic(
7070
this.unsubscriber = unsubscriber;
7171
}
7272

73+
@Override
74+
/** {@inheritDoc} */
75+
public String getType() {
76+
return type;
77+
}
78+
7379
@Override
7480
/** {@inheritDoc} */
7581
public final CompletableFuture<JsonObject> toJson(int iid) {

src/main/java/io/github/hapjava/server/impl/HomekitRegistry.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.github.hapjava.accessories.HomekitAccessory;
44
import io.github.hapjava.characteristics.Characteristic;
5+
import io.github.hapjava.server.impl.connections.SubscriptionManager;
56
import io.github.hapjava.services.Service;
67
import io.github.hapjava.services.impl.AccessoryInformationService;
78
import java.util.ArrayList;
@@ -19,14 +20,16 @@ public class HomekitRegistry {
1920
private static final Logger logger = LoggerFactory.getLogger(HomekitRegistry.class);
2021

2122
private final String label;
23+
private final SubscriptionManager subscriptions;
2224
private final Map<Integer, HomekitAccessory> accessories;
2325
private final Map<HomekitAccessory, Map<Integer, Service>> services = new HashMap<>();
2426
private final Map<HomekitAccessory, Map<Integer, Characteristic>> characteristics =
2527
new HashMap<>();
2628
private boolean isAllowUnauthenticatedRequests = false;
2729

28-
public HomekitRegistry(String label) {
30+
public HomekitRegistry(String label, SubscriptionManager subscriptions) {
2931
this.label = label;
32+
this.subscriptions = subscriptions;
3033
this.accessories = new ConcurrentHashMap<>();
3134
reset();
3235
}
@@ -61,6 +64,7 @@ public synchronized void reset() {
6164
services.put(accessory, newServicesByInterfaceId);
6265
characteristics.put(accessory, newCharacteristicsByInterfaceId);
6366
}
67+
subscriptions.resync(this);
6468
}
6569

6670
public String getLabel() {
@@ -87,8 +91,8 @@ public void add(HomekitAccessory accessory) {
8791
accessories.put(accessory.getId(), accessory);
8892
}
8993

90-
public void remove(HomekitAccessory accessory) {
91-
accessories.remove(accessory.getId());
94+
public boolean remove(HomekitAccessory accessory) {
95+
return accessories.remove(accessory.getId()) != null;
9296
}
9397

9498
public boolean isAllowUnauthenticatedRequests() {

src/main/java/io/github/hapjava/server/impl/HomekitRoot.java

+29-15
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public class HomekitRoot {
3636
private final SubscriptionManager subscriptions = new SubscriptionManager();
3737
private boolean started = false;
3838
private int configurationIndex = 1;
39+
private int nestedBatches = 0;
40+
private boolean madeChanges = false;
3941

4042
HomekitRoot(
4143
String label, HomekitWebHandler webHandler, InetAddress host, HomekitAuthInfo authInfo)
@@ -65,7 +67,7 @@ public class HomekitRoot {
6567
this.authInfo = authInfo;
6668
this.label = label;
6769
this.category = category;
68-
this.registry = new HomekitRegistry(label);
70+
this.registry = new HomekitRegistry(label, subscriptions);
6971
}
7072

7173
HomekitRoot(
@@ -83,11 +85,19 @@ public class HomekitRoot {
8385
this(
8486
label, DEFAULT_ACCESSORY_CATEGORY, webHandler, authInfo, new JmdnsHomekitAdvertiser(jmdns));
8587
}
88+
89+
public synchronized void batchUpdate() {
90+
if (this.nestedBatches == 0) madeChanges = false;
91+
++this.nestedBatches;
92+
}
93+
94+
public synchronized void completeUpdateBatch() {
95+
if (--this.nestedBatches == 0 && madeChanges) registry.reset();
96+
}
97+
8698
/**
87-
* Add an accessory to be handled and advertised by this root. Any existing HomeKit connections
88-
* will be terminated to allow the clients to reconnect and see the updated accessory list. When
89-
* using this for a bridge, the ID of the accessory must be greater than 1, as that ID is reserved
90-
* for the Bridge itself.
99+
* Add an accessory to be handled and advertised by this root. When using this for a bridge, the
100+
* ID of the accessory must be greater than 1, as that ID is reserved for the Bridge itself.
91101
*
92102
* @param accessory to advertise and handle.
93103
*/
@@ -110,25 +120,28 @@ void addAccessorySkipRangeCheck(HomekitAccessory accessory) {
110120
if (logger.isTraceEnabled()) {
111121
accessory.getName().thenAccept(name -> logger.trace("Added accessory {}", name));
112122
}
113-
if (started) {
123+
madeChanges = true;
124+
if (started && nestedBatches == 0) {
114125
registry.reset();
115126
}
116127
}
117128

118129
/**
119-
* Removes an accessory from being handled or advertised by this root. Any existing HomeKit
120-
* connections will be terminated to allow the clients to reconnect and see the updated accessory
121-
* list.
130+
* Removes an accessory from being handled or advertised by this root.
122131
*
123132
* @param accessory accessory to cease advertising and handling
124133
*/
125134
public void removeAccessory(HomekitAccessory accessory) {
126-
this.registry.remove(accessory);
127-
if (logger.isTraceEnabled()) {
128-
accessory.getName().thenAccept(name -> logger.trace("Removed accessory {}", name));
129-
}
130-
if (started) {
131-
registry.reset();
135+
if (this.registry.remove(accessory)) {
136+
if (logger.isTraceEnabled()) {
137+
accessory.getName().thenAccept(name -> logger.trace("Removed accessory {}", name));
138+
}
139+
madeChanges = true;
140+
if (started && nestedBatches == 0) {
141+
registry.reset();
142+
}
143+
} else {
144+
accessory.getName().thenAccept(name -> logger.warn("Could not remove accessory {}", name));
132145
}
133146
}
134147

@@ -140,6 +153,7 @@ public void removeAccessory(HomekitAccessory accessory) {
140153
*/
141154
public void start() {
142155
started = true;
156+
madeChanges = false;
143157
registry.reset();
144158
webHandler
145159
.start(

0 commit comments

Comments
 (0)