Skip to content

Commit 7cf1b25

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

18 files changed

Lines changed: 255 additions & 80 deletions

File tree

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This binding provides the following Thing types. The Thing type IDs match the de
2121

2222
| Name | Type | Description | Default | Required | Advanced |
2323
|-------------------|---------|-----------------------------------------|-----------------|----------|----------|
24-
| hostname | text | Hostname or base URL of the ntfy server | [https://ntfy.sh](https://ntfy.sh) | yes | no |
24+
| hostname | text | Base URL of the ntfy server | [https://ntfy.sh](https://ntfy.sh) | yes | no |
2525
| username | text | Optional username for basic auth | N/A | no | no |
2626
| password | text | Optional password for basic auth | N/A | no | no |
2727
| connectionTimeout | integer | WebSocket / HTTP connection timeout ms | 60000 | no | yes |
@@ -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/NtfyConnectionConfiguration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
public class NtfyConnectionConfiguration {
2828

2929
/**
30-
* Hostname of the ntfy server, e.g. "ntfy.sh"
30+
* Hostname of the ntfy server, e.g. "https://ntfy.sh"
3131
*/
3232
public String hostname = "";
3333

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

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public synchronized void createAndRegisterWebSocketClient(Thing thing) {
9393
} catch (Exception e) {
9494
logger.error("Error stopping WebSocketConnection", e);
9595
}
96+
webSocketConnections.remove(thing.getUID());
9697
}
9798

9899
webSocketConnections.put(thing.getUID(), newClient);
@@ -129,10 +130,24 @@ public synchronized boolean startWebSocketConnection(Thing topicThing, NtfyWebSo
129130
client.start();
130131
client.setMaxIdleTimeout(config.connectionTimeout);
131132

133+
String webSocketScheme = "wss";
134+
switch ((new URI(config.hostname)).getScheme()) {
135+
case "http":
136+
webSocketScheme = "ws";
137+
break;
138+
case "https":
139+
webSocketScheme = "wss";
140+
break;
141+
default:
142+
throw new IllegalArgumentException("Unsupported URI scheme: "
143+
+ (new URI(config.hostname)).getScheme() + " - only http and https are supported");
144+
}
145+
132146
client.connect(ntfyWebSocket,
133-
new URI("wss:"
147+
new URI(webSocketScheme + ":"
134148
+ (new URI(config.hostname + "/" + topicName + "/ws")).getRawSchemeSpecificPart()),
135149
setupRequest());
150+
updateStatus(ThingStatus.ONLINE);
136151
} catch (Exception e) {
137152
logger.error("Error creating WebSocketConnection", e);
138153
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage());
@@ -142,6 +157,22 @@ public synchronized boolean startWebSocketConnection(Thing topicThing, NtfyWebSo
142157
return true;
143158
}
144159

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

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

Lines changed: 76 additions & 15 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) (bridge.getHandler());
7279
}
7380

7481
@Override
@@ -80,12 +87,22 @@ 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
87104
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
88-
if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE
105+
if ((bridgeStatusInfo.getStatus() == ThingStatus.ONLINE || bridgeStatusInfo.getStatus() == ThingStatus.UNKNOWN)
89106
&& getThing().getStatusInfo().getStatusDetail() == ThingStatusDetail.BRIDGE_OFFLINE) {
90107
updateStatus(ThingStatus.UNKNOWN);
91108
initialize();
@@ -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/HttpActionButton.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ public class HttpActionButton extends ActionButtonBase {
4242
* @param headers optional headers to include (may be null)
4343
* @param body optional request body (may be null)
4444
* @throws MalformedURLException when the provided URL is not a valid URL
45+
* @throws IllegalArgumentException when the provided URL is not valid
4546
*/
4647
public HttpActionButton(String label, Boolean clearNotification, String url, @Nullable String method,
47-
@Nullable String headers, @Nullable String body) throws MalformedURLException {
48+
@Nullable String headers, @Nullable String body) throws IllegalArgumentException, MalformedURLException {
4849
super(label, clearNotification);
4950

5051
this.url = URI.create(url).toURL();

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)