Skip to content

Commit fc9b9f5

Browse files
committed
code review changes
Signed-off-by: Christian Kittel <ckittel@gmx.de>
1 parent e4107d8 commit fc9b9f5

14 files changed

Lines changed: 220 additions & 66 deletions

File tree

bundles/org.openhab.binding.ntfy/README.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,11 @@ Configure the `ntfyConnection` as a bridge to hold shared server and authenticat
3838

3939
### `ntfyTopic` Channels
4040

41-
| Channel | Type | Read/Write | Description |
42-
|-------------|----------|------------|---------------------------------------------|
43-
| lastMessage | String | R | Last received message payload (read-only) |
44-
| messageTime | DateTime | R | Timestamp of the last message (read-only) |
41+
| Channel | Type | Read/Write | Description |
42+
|-----------------|----------|------------|---------------------------------------------|
43+
| lastMessage | String | R | Last received message payload (read-only) |
44+
| lastMessageTime | DateTime | R | Timestamp of the last message (read-only) |
45+
| lastMessageId | String | R | ID of the last message (read-only) |
4546

4647
## Full Example
4748

@@ -95,7 +96,7 @@ Below is a quick reference of the builder-style methods provided by the binding
9596
| Method | Parameters | Description | Ntfy docs |
9697
|--------|------------|-------------|----------|
9798
| withMessage | (String message) | Set the main message text. | [publish](https://docs.ntfy.sh/publish/#message-title) |
98-
| withPriority | (int priority) | Set message priority (0-5). | [priority](https://ntfy.sh/docs/publish/#priority) |
99+
| withPriority | (int priority) | Set message priority (1-5). | [priority](https://ntfy.sh/docs/publish/#priority) |
99100
| withTag | (String tag) | Add a tag to the message. | [tags](https://ntfy.sh/docs/publish/#tags) |
100101
| withIcon | (String url) | Set an icon URL for the notification. | [icons](https://ntfy.sh/docs/publish/#icons) |
101102
| withAttachment | (String url, String filename) | Attach a file or resource URL with optional filename. | [attachments](https://docs.ntfy.sh/publish/#attach-file-from-a-url) |

bundles/org.openhab.binding.ntfy/src/main/java/org/openhab/binding/ntfy/internal/NtfyBindingConstants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,5 @@ public class NtfyBindingConstants {
3333
// List of all Channel ids
3434
public static final String CHANNEL_NTFY_LASTMESSAGE = "lastMessage";
3535
public static final String CHANNEL_NTFY_LASTMESSAGETIME = "lastMessageTime";
36-
public static final String CHANNEL_NTFY_LASTMESSAGEID = "lastMessageMessageId";
36+
public static final String CHANNEL_NTFY_LASTMESSAGEID = "lastMessageId";
3737
}

bundles/org.openhab.binding.ntfy/src/main/java/org/openhab/binding/ntfy/internal/NtfyConnectionHandler.java

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,10 +129,24 @@ public synchronized boolean startWebSocketConnection(Thing topicThing, NtfyWebSo
129129
client.start();
130130
client.setMaxIdleTimeout(config.connectionTimeout);
131131

132+
String webSocketScheme = "wss";
133+
switch ((new URI(config.hostname)).getScheme()) {
134+
case "http":
135+
webSocketScheme = "ws";
136+
break;
137+
case "https":
138+
webSocketScheme = "wss";
139+
break;
140+
default:
141+
throw new IllegalArgumentException(
142+
"Unsupported URI scheme: " + (new URI(config.hostname)).getScheme());
143+
}
144+
132145
client.connect(ntfyWebSocket,
133-
new URI("wss:"
146+
new URI(webSocketScheme + ":"
134147
+ (new URI(config.hostname + "/" + topicName + "/ws")).getRawSchemeSpecificPart()),
135148
setupRequest());
149+
updateStatus(ThingStatus.ONLINE);
136150
} catch (Exception e) {
137151
logger.error("Error creating WebSocketConnection", e);
138152
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage());
@@ -142,6 +156,22 @@ public synchronized boolean startWebSocketConnection(Thing topicThing, NtfyWebSo
142156
return true;
143157
}
144158

159+
private synchronized void restartRegisteredWebSocketClients() {
160+
for (Map.Entry<ThingUID, WebSocketClient> entry : webSocketConnections.entrySet()) {
161+
WebSocketClient client = entry.getValue();
162+
try {
163+
if (client.isStarted()) {
164+
client.stop();
165+
}
166+
client.start();
167+
updateStatus(ThingStatus.ONLINE);
168+
169+
} catch (Exception e) {
170+
logger.debug("Error restarting WebSocketConnection for thing {}", entry.getKey(), e);
171+
}
172+
}
173+
}
174+
145175
private String getTopicNameFromThing(Thing topicThing) {
146176
return topicThing.getConfiguration().as(NtfyTopicConfiguration.class).topicname;
147177
}
@@ -154,6 +184,21 @@ private String getTopicNameFromThing(Thing topicThing) {
154184
*/
155185
public void connectionError(Throwable cause) {
156186
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, cause.getLocalizedMessage());
157-
scheduler.schedule(() -> updateStatus(ThingStatus.ONLINE), 30, TimeUnit.SECONDS);
187+
scheduler.schedule(() -> restartRegisteredWebSocketClients(), 30, TimeUnit.SECONDS);
188+
}
189+
190+
@Override
191+
public void dispose() {
192+
super.dispose();
193+
194+
webSocketConnections.values().forEach(client -> {
195+
try {
196+
if (client.isRunning()) {
197+
client.stop();
198+
}
199+
} catch (Exception e) {
200+
logger.error("Error stopping WebSocketConnection", e);
201+
}
202+
});
158203
}
159204
}

bundles/org.openhab.binding.ntfy/src/main/java/org/openhab/binding/ntfy/internal/NtfyTopicHandler.java

Lines changed: 75 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.openhab.binding.ntfy.internal.network.WebSocketConnectionListener;
2929
import org.openhab.core.library.types.DateTimeType;
3030
import org.openhab.core.library.types.StringType;
31+
import org.openhab.core.thing.Bridge;
3132
import org.openhab.core.thing.ChannelUID;
3233
import org.openhab.core.thing.Thing;
3334
import org.openhab.core.thing.ThingStatus;
@@ -48,9 +49,10 @@
4849
public class NtfyTopicHandler extends BaseThingHandler implements WebSocketConnectionListener {
4950

5051
private NtfyWebSocket ntfyWebSocket;
51-
private NtfySender ntfySender;
52+
private @Nullable NtfySender ntfySender;
5253
private boolean isInitializing;
5354
private @Nullable MessageEvent lastMessageEvent;
55+
private HttpClient httpClient;
5456

5557
/**
5658
* Creates a new {@link NtfyTopicHandler} for the provided topic thing.
@@ -61,14 +63,19 @@ public class NtfyTopicHandler extends BaseThingHandler implements WebSocketConne
6163
public NtfyTopicHandler(Thing thing, HttpClient httpClient) {
6264
super(thing);
6365

66+
this.httpClient = httpClient;
6467
this.ntfyWebSocket = new NtfyWebSocket(this);
65-
this.ntfySender = new NtfySender(this.getConfigAs(NtfyTopicConfiguration.class).topicname, httpClient,
66-
this::getBridgeHandler);
6768
}
6869

69-
private NtfyConnectionHandler getBridgeHandler() {
70-
return (NtfyConnectionHandler) (java.util.Objects
71-
.requireNonNull(java.util.Objects.requireNonNull(getBridge()).getHandler()));
70+
private @Nullable NtfyConnectionHandler getBridgeHandler() {
71+
@Nullable
72+
Bridge bridge = getBridge();
73+
74+
if (bridge == null) {
75+
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
76+
return null;
77+
}
78+
return (NtfyConnectionHandler) (java.util.Objects.requireNonNull(getBridge()).getHandler());
7279
}
7380

7481
@Override
@@ -80,7 +87,17 @@ public Collection<Class<? extends ThingHandlerService>> getServices() {
8087
public void thingUpdated(Thing thing) {
8188
super.thingUpdated(thing);
8289

83-
getBridgeHandler().createAndRegisterWebSocketClient(thing);
90+
@Nullable
91+
NtfyConnectionHandler bridgeHandler = getBridgeHandler();
92+
93+
if (bridgeHandler == null) {
94+
return;
95+
}
96+
97+
ntfySender = new NtfySender(this.getConfigAs(NtfyTopicConfiguration.class).topicname, httpClient,
98+
this::getBridgeHandler);
99+
bridgeHandler.createAndRegisterWebSocketClient(thing);
100+
startConnection();
84101
}
85102

86103
@Override
@@ -112,7 +129,14 @@ public void initialize() {
112129

113130
scheduler.execute(() -> {
114131
try {
115-
getBridgeHandler().createAndRegisterWebSocketClient(thing);
132+
ntfySender = new NtfySender(this.getConfigAs(NtfyTopicConfiguration.class).topicname, httpClient,
133+
this::getBridgeHandler);
134+
@Nullable
135+
NtfyConnectionHandler bridgeHandler = getBridgeHandler();
136+
if (bridgeHandler == null) {
137+
return;
138+
}
139+
bridgeHandler.createAndRegisterWebSocketClient(thing);
116140
startConnection();
117141
} finally {
118142
this.isInitializing = false;
@@ -122,7 +146,12 @@ public void initialize() {
122146

123147
private void startConnection() {
124148
if (getThing().getStatus() != ThingStatus.ONLINE) {
125-
if (!getBridgeHandler().startWebSocketConnection(thing, ntfyWebSocket)) {
149+
@Nullable
150+
NtfyConnectionHandler bridgeHandler = getBridgeHandler();
151+
if (bridgeHandler == null) {
152+
return;
153+
}
154+
if (!bridgeHandler.startWebSocketConnection(thing, ntfyWebSocket)) {
126155
updateStatus(ThingStatus.OFFLINE);
127156
}
128157
}
@@ -135,15 +164,22 @@ public void connectionEstablished() {
135164

136165
@Override
137166
public void connectionLost(String reason) {
167+
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
168+
reason.isBlank() ? "WebSocket connection lost" : reason);
138169
}
139170

140171
@Override
141172
public void connectionError(Throwable cause) {
142-
getBridgeHandler().connectionError(cause);
173+
@Nullable
174+
NtfyConnectionHandler bridgeHandler = getBridgeHandler();
175+
if (bridgeHandler == null) {
176+
return;
177+
}
178+
bridgeHandler.connectionError(cause);
143179
}
144180

145181
@Override
146-
public void messageRecieved(BaseEvent event) {
182+
public void messageReceived(BaseEvent event) {
147183
if (event instanceof MessageEvent message) {
148184
this.lastMessageEvent = message;
149185
updateChannels();
@@ -169,19 +205,40 @@ private void updateChannels() {
169205
* @throws URISyntaxException
170206
*/
171207
public String sendMessage(NtfyMessage ntfyMessage) throws URISyntaxException {
208+
final @Nullable NtfySender sender = ntfySender;
209+
if (sender == null) {
210+
return "";
211+
}
212+
172213
@Nullable
173-
MessageEvent sendMessage = ntfySender.sendMessage(ntfyMessage);
214+
MessageEvent sendMessage = sender.sendMessage(ntfyMessage);
174215

175216
if (sendMessage == null) {
176217
return "";
177218
}
178219
return sendMessage.getId();
179220
}
180221

222+
/**
223+
* Uploads a local file to the configured topic via the underlying
224+
* {@link NtfySender#sendFile(String, String, String)} and returns the
225+
* created message id on success.
226+
*
227+
* @param file the filesystem path to the file to upload
228+
* @param filename optional filename to present to recipients (may be null)
229+
* @param sequenceId optional sequence id to associate with the uploaded message
230+
* @return the created message id on success, or an empty string on failure
231+
* @throws URISyntaxException when the constructed request URI is invalid
232+
*/
181233
public String sendFile(String file, @Nullable String filename, @Nullable String sequenceId)
182234
throws URISyntaxException {
235+
final @Nullable NtfySender sender = ntfySender;
236+
if (sender == null) {
237+
return "";
238+
}
239+
183240
@Nullable
184-
MessageEvent sendMessage = ntfySender.sendFile(file, filename, sequenceId);
241+
MessageEvent sendMessage = sender.sendFile(file, filename, sequenceId);
185242

186243
if (sendMessage == null) {
187244
return "";
@@ -197,6 +254,10 @@ public String sendFile(String file, @Nullable String filename, @Nullable String
197254
* @throws URISyntaxException when the underlying request URI could not be constructed
198255
*/
199256
public boolean deleteMessage(String sequenceId) throws URISyntaxException {
200-
return ntfySender.deleteMessage(sequenceId);
257+
final @Nullable NtfySender sender = ntfySender;
258+
if (sender == null) {
259+
return false;
260+
}
261+
return sender.deleteMessage(sequenceId);
201262
}
202263
}

bundles/org.openhab.binding.ntfy/src/main/java/org/openhab/binding/ntfy/internal/action/NtfyActions.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,13 @@ public static ThingActions withViewAction(ThingActions actions, String label, Bo
267267
* @param actions the ThingActions instance
268268
* @param label the action label
269269
* @param clearNotification whether to clear the notification
270-
* @param url the value to copy
270+
* @param value the value to copy
271271
* @return the modified ThingActions builder instance
272272
* @throws MalformedURLException never thrown by this wrapper but declared for API compatibility
273273
*/
274-
public static ThingActions withCopyAction(ThingActions actions, String label, Boolean clearNotification, String url)
275-
throws MalformedURLException {
276-
return ((NtfyActions) actions).withCopyAction(label, clearNotification, url);
274+
public static ThingActions withCopyAction(ThingActions actions, String label, Boolean clearNotification,
275+
String value) throws MalformedURLException {
276+
return ((NtfyActions) actions).withCopyAction(label, clearNotification, value);
277277
}
278278

279279
/**
@@ -412,6 +412,12 @@ public static ThingActions withSequenceId(ThingActions actions, @Nullable String
412412
if (handler == null) {
413413
return "";
414414
}
415+
416+
if (!ntfyMessage.hasMessage() || ntfyMessage.getMessage().isBlank()) {
417+
throw new IllegalStateException(
418+
"Cannot send message without content. Please set a message via withMessage() before sending.");
419+
}
420+
415421
return handler.sendMessage(ntfyMessage);
416422
}
417423

bundles/org.openhab.binding.ntfy/src/main/java/org/openhab/binding/ntfy/internal/network/GsonDeserializer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
import com.google.gson.Gson;
2222
import com.google.gson.GsonBuilder;
23+
import com.google.gson.JsonSyntaxException;
2324

2425
/**
2526
* Helper wrapper around Gson used to deserialize incoming ntfy JSON messages
@@ -39,7 +40,7 @@ public class GsonDeserializer {
3940
* @param message the raw JSON message
4041
* @return the deserialized {@link BaseEvent} or {@code null} when parsing failed
4142
*/
42-
public static @Nullable BaseEvent deserialize(String message) {
43+
public static @Nullable BaseEvent deserialize(String message) throws JsonSyntaxException {
4344
return GSON.fromJson(message, BaseEvent.class);
4445
}
4546

bundles/org.openhab.binding.ntfy/src/main/java/org/openhab/binding/ntfy/internal/network/NtfyMessage.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import java.net.MalformedURLException;
1616
import java.net.URI;
1717
import java.net.URISyntaxException;
18-
import java.util.HashSet;
18+
import java.util.LinkedHashSet;
1919
import java.util.Set;
2020

2121
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -31,8 +31,8 @@ public class NtfyMessage {
3131

3232
private @Nullable String message;
3333
private int priority = 3;
34-
private HashSet<String> tags = new HashSet<>();
35-
private HashSet<ActionButtonBase> actions = new HashSet<>();
34+
private LinkedHashSet<String> tags = new LinkedHashSet<>();
35+
private LinkedHashSet<ActionButtonBase> actions = new LinkedHashSet<>();
3636
private @Nullable URI clickAction;
3737
private @Nullable URI icon;
3838
private @Nullable URI attachment;
@@ -76,7 +76,7 @@ public boolean hasMessage() {
7676
*/
7777
public void setPriority(int priority) {
7878
if (priority < 1 || priority > 5) {
79-
throw new IllegalArgumentException();
79+
throw new IllegalArgumentException("Invalid priority " + priority + ". Allowed range is 1..5.");
8080
}
8181
this.priority = priority;
8282
}

0 commit comments

Comments
 (0)