Skip to content

Commit a8032cd

Browse files
committed
Release 0.9.1 (Fix errors of MQTT module if server is not specified); Add tray icon
1 parent 630a4bc commit a8032cd

32 files changed

+294
-40
lines changed

iopc-server/build.gradle

+30-13
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ plugins {
1212
apply plugin: 'io.spring.dependency-management'
1313

1414
group = 'ru.cubly.iopc'
15-
version = '0.9.0'
15+
version = '0.9.1'
1616

1717
processResources {
1818
filesMatching('application.properties') {
@@ -29,26 +29,36 @@ setupBuilder {
2929
application = "IoPC Agent"
3030
appIdentifier = "iopc-agent"
3131
licenseFile = '../LICENSE.txt'
32-
icons = ['src/main/resources/icon/icon16.png',
33-
'src/main/resources/icon/icon32.png',
34-
'src/main/resources/icon/icon48.png',
35-
'src/main/resources/icon/icon64.png',
36-
'src/main/resources/icon/icon128.png'
32+
icons = [
33+
"src/main/resources/static/icon/icon16.png",
34+
"src/main/resources/static/icon/icon24.png",
35+
"src/main/resources/static/icon/icon32.png",
36+
"src/main/resources/static/icon/icon48.png",
37+
"src/main/resources/static/icon/icon64.png",
38+
"src/main/resources/static/icon/icon128.png",
39+
"src/main/resources/static/icon/icon256.png",
40+
"src/main/resources/static/icon/icon512.png"
3741
]
3842
failOnEmptyFrom = true
3943
from bootJar.outputs
4044
mainClass = "org.springframework.boot.loader.JarLauncher"
4145
mainJar = "iopc-server-${version}.jar"
4246

43-
bundleJre = new File( System.getProperty('java.home') )
47+
bundleJre = new File(System.getProperty('java.home'))
4448
bundleJreTarget = "javaruntime"
4549

46-
service {
50+
desktopStarter {
51+
4752
displayName = "IoPC Agent"
4853
description = "Internet of PC (IoPC) Agent helps you to integrate PC into your smart home system"
49-
executable = "iopc-server.exe"
54+
55+
// an alternative executable. Overrides the mainJar and mainClass
56+
// either relative to installation root or absolute
57+
executable = "iopc-agent.exe"
58+
59+
// The working directory of the service, relative to installation root
5060
workDir = "."
51-
startOnBoot = true
61+
5262
}
5363
}
5464

@@ -57,12 +67,17 @@ msi {
5767
installScope = "perMachine"
5868

5969
launch4j {
60-
displayName = "Internet of PC agent (IoPC)"
61-
description = "IoPC helps you to integrate PC into your smart home system"
70+
displayName = "Internet of PC agent (IoPC)"
71+
description = "IoPC helps you to integrate PC into your smart home system"
6272

63-
executable = "iopc-server.exe"
73+
executable = "iopc-agent.exe"
74+
// requestedExecutionLevel = "asInvoker"
6475
}
6576

77+
minOS = 6.1
78+
79+
// wxsTemplate = 'installer/template.wsx'
80+
6681
bannerBmp = 'installer/TopBanner.bmp'
6782
dialogBmp = 'installer/DialogBanner.bmp'
6883
}
@@ -88,6 +103,8 @@ dependencies {
88103
implementation 'org.springframework.boot:spring-boot-starter-validation'
89104
implementation 'org.springframework.integration:spring-integration-mqtt:5.4.4'
90105
implementation 'org.springframework.integration:spring-integration-websocket:5.4.4'
106+
107+
implementation 'org.springframework.cloud:spring-cloud-starter'
91108
implementation 'org.springframework.cloud:spring-cloud-starter-config'
92109

93110
implementation 'org.jmdns:jmdns:3.5.1'

iopc-server/installer/template.wsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
3+
<Product>
4+
5+
<Directory Id="TARGETDIR" Name="SourceDir">
6+
<!-- Auto-start via Registry -->
7+
<Component Id="IoPCAgentAutostart" Guid="ca29d30d-c217-4050-84fa-a4864c5d75eb">
8+
<RegistryValue Id="IoPC.rst" Root="HKCU" Action="write"
9+
Key="Software\Microsoft\Windows\CurrentVersion\Run"
10+
Name="IoPC Agent"
11+
Value="[INSTALLDIR]iopc-agent.exe"
12+
Type="string"/>
13+
</Component>
14+
</Directory>
15+
16+
<Feature Id="MainApplication">
17+
<ComponentRef Id="IoPCAgentAutostart"/>
18+
</Feature>
19+
</Product>
20+
</Wix>

iopc-server/src/main/java/ru/cubly/iopc/IoPCApplication.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,10 @@
2020
@SpringBootApplication
2121
@ConfigurationPropertiesScan("ru.cubly.iopc")
2222
public class IoPCApplication {
23-
static {
24-
System.setProperty("java.awt.headless", "false");
25-
}
2623

2724
public static void main(String[] args) {
2825
new SpringApplicationBuilder(IoPCApplication.class)
26+
.headless(false)
2927
.run(args);
3028
}
3129

iopc-server/src/main/java/ru/cubly/iopc/config/FlowConfig.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
@Configuration
1111
public class FlowConfig {
1212
@Bean
13-
public IntegrationFlow loggingFlow() {
13+
public IntegrationFlow routerLoggingFlow() {
1414
return IntegrationFlows.from("defaultRouterChannel")
1515
.log(LoggingHandler.Level.WARN, "ROUTER_LOGGER",
1616
m -> "Route not found for message: service="

iopc-server/src/main/java/ru/cubly/iopc/module/mqtt/MqttConfig.java

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import lombok.extern.log4j.Log4j2;
44
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
5+
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
6+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
57
import org.springframework.context.annotation.Bean;
68
import org.springframework.context.annotation.Configuration;
79
import org.springframework.integration.channel.DirectChannel;
@@ -17,6 +19,8 @@
1719
import java.nio.charset.StandardCharsets;
1820

1921
@Configuration
22+
@ConditionalOnProperty("mqtt.server-uri")
23+
@ConditionalOnExpression("'${mqtt.server-uri}'!=null && '${mqtt.server-uri}'!=''")
2024
@Log4j2
2125
public class MqttConfig {
2226
public static final int STATE_QOS = 1;

iopc-server/src/main/java/ru/cubly/iopc/module/mqtt/MqttModule.java

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package ru.cubly.iopc.module.mqtt;
22

33
import lombok.extern.log4j.Log4j2;
4+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
5+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
6+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
47
import org.springframework.cloud.context.config.annotation.RefreshScope;
58
import org.springframework.context.annotation.Bean;
69
import org.springframework.integration.annotation.MessagingGateway;
@@ -58,6 +61,7 @@ public Class<? extends IntentPayload> getPayloadType(String action) {
5861
}
5962

6063
@Bean
64+
@ConditionalOnBean(MqttConfig.class)
6165
public IntegrationFlow mqttInbound(MessageProducerSupport mqttMessageDrivenChannelAdapter) {
6266
return FlowUtils.moduleRouterFlow(
6367
mqttMessageDrivenChannelAdapter,
@@ -73,6 +77,7 @@ public interface MqttMessagingTemplate {
7377
}
7478

7579
@Bean
80+
@ConditionalOnBean(MqttConfig.class)
7681
public IntegrationFlow mqttOutbound(MessageHandler mqttOutboundMessageHandler) {
7782
return FlowUtils.forService(this, ACTION_SEND)
7883
.enrichHeaders(h -> h.headerExpression(MqttHeaders.TOPIC,
@@ -85,9 +90,12 @@ public IntegrationFlow mqttOutbound(MessageHandler mqttOutboundMessageHandler) {
8590
.get();
8691
}
8792

88-
@Scheduled(fixedRate = 30000, initialDelay = 1000)
89-
public void sendScheduledReport() {
90-
mqttMessagingTemplate.send(new MqttPayload(STATE_TOPIC, STATE_ONLINE, STATE_QOS, true));
93+
@Bean
94+
@ConditionalOnMissingBean(MqttConfig.class)
95+
public IntegrationFlow mqttOutboundBlackHole() {
96+
return FlowUtils.forService(this, ACTION_SEND)
97+
.handle((o, h) -> null)
98+
.get();
9199
}
92100

93101
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package ru.cubly.iopc.module.mqtt;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
5+
import org.springframework.scheduling.annotation.Scheduled;
6+
import org.springframework.stereotype.Component;
7+
8+
import static ru.cubly.iopc.module.mqtt.MqttConfig.STATE_ONLINE;
9+
import static ru.cubly.iopc.module.mqtt.MqttConfig.STATE_QOS;
10+
import static ru.cubly.iopc.module.mqtt.MqttUtil.STATE_TOPIC;
11+
12+
@Component
13+
@ConditionalOnBean(MqttConfig.class)
14+
@RequiredArgsConstructor
15+
public class MqttOnlineActuator {
16+
private final MqttModule.MqttMessagingTemplate mqttMessagingTemplate;
17+
18+
@Scheduled(fixedRate = 30000, initialDelay = 1000)
19+
public void sendScheduledReport() {
20+
mqttMessagingTemplate.send(new MqttPayload(STATE_TOPIC, STATE_ONLINE, STATE_QOS, true));
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package ru.cubly.iopc.util;
2+
3+
import lombok.extern.log4j.Log4j2;
4+
import org.springframework.beans.factory.annotation.Value;
5+
import org.springframework.boot.SpringApplication;
6+
import org.springframework.cloud.context.restart.RestartEndpoint;
7+
import org.springframework.context.ApplicationContext;
8+
import org.springframework.stereotype.Component;
9+
10+
import javax.annotation.PostConstruct;
11+
import javax.annotation.PreDestroy;
12+
import javax.swing.*;
13+
import java.awt.*;
14+
import java.io.IOException;
15+
import java.net.URI;
16+
import java.net.URISyntaxException;
17+
import java.net.URL;
18+
19+
@Component
20+
@Log4j2
21+
public class IoPCTrayIcon extends TrayIcon {
22+
23+
private static final String IMAGE_PATH = "/static/icon/icon16.png";
24+
private static final String TOOLTIP = "IoPC Agent";
25+
26+
private final PopupMenu popup;
27+
private final SystemTray tray;
28+
29+
private final String version;
30+
private final Integer port;
31+
private final RestartEndpoint restartEndpoint;
32+
private final ApplicationContext appContext;
33+
34+
public IoPCTrayIcon(
35+
@Value("${app.build-info.version}") String version,
36+
@Value("${server.port}") Integer port,
37+
RestartEndpoint restartEndpoint, ApplicationContext appContext) {
38+
super(createImage(IMAGE_PATH, TOOLTIP), TOOLTIP);
39+
40+
this.version = version;
41+
this.port = port;
42+
this.restartEndpoint = restartEndpoint;
43+
this.appContext = appContext;
44+
45+
popup = new PopupMenu();
46+
tray = SystemTray.getSystemTray();
47+
}
48+
49+
@PostConstruct
50+
private void setup() throws AWTException {
51+
if (!SystemTray.isSupported()) {
52+
log.warn("Tray is not supported in this environment");
53+
return;
54+
}
55+
56+
MenuItem aboutItem = new MenuItem("IoPC v" + version + " is running");
57+
aboutItem.setEnabled(false);
58+
59+
MenuItem configuratorItem = new MenuItem("Preferences");
60+
MenuItem restartItem = new MenuItem("Restart");
61+
MenuItem exitItem = new MenuItem("Exit");
62+
63+
configuratorItem.addActionListener(e -> {
64+
if (Desktop.isDesktopSupported() && Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)) {
65+
try {
66+
Desktop.getDesktop().browse(new URI("http://127.0.0.1:" + port));
67+
} catch (IOException | URISyntaxException exception) {
68+
log.error("Could not open browser", exception);
69+
}
70+
} else {
71+
log.error("Could not open browser, this feature is not supported in this environment");
72+
}
73+
});
74+
restartItem.addActionListener(e -> {
75+
restartEndpoint.restart();
76+
configuratorItem.setEnabled(false);
77+
restartItem.setEnabled(false);
78+
restartItem.setLabel("Restarting...");
79+
exitItem.setEnabled(false);
80+
});
81+
exitItem.addActionListener(e -> {
82+
SpringApplication.exit(appContext, () -> 0);
83+
configuratorItem.setEnabled(false);
84+
restartItem.setEnabled(false);
85+
exitItem.setEnabled(false);
86+
exitItem.setLabel("Exiting...");
87+
});
88+
89+
popup.add(aboutItem);
90+
popup.add(configuratorItem);
91+
popup.add(restartItem);
92+
popup.add(exitItem);
93+
94+
// popup.add(itemAbout);
95+
// here add the items to your popup menu. These extend MenuItem
96+
// popup.addSeparator();
97+
setPopupMenu(popup);
98+
tray.add(this);
99+
}
100+
101+
@PreDestroy
102+
public void destroy() {
103+
if (!SystemTray.isSupported()) {
104+
return;
105+
}
106+
107+
tray.remove(this);
108+
}
109+
110+
protected static Image createImage(String path, String description) {
111+
URL imageURL = IoPCTrayIcon.class.getResource(path);
112+
return new ImageIcon(imageURL, description).getImage();
113+
}
114+
}

iopc-server/src/main/resources/application.properties

+2-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ spring.web.locale=\${server.language}
55
spring.web.locale-resolver=fixed
66

77
management.endpoint.restart.enabled=true
8+
management.endpoints.web.exposure.include=restart,health
89

910
app.build-info.version=${version}
1011

@@ -16,7 +17,7 @@ logging.level.org.jnativehook=warn
1617
spring.config.import=optional:file:iopc-overrides.properties
1718
spring.jackson.serialization.write-dates-as-timestamps=false
1819

19-
mqtt.server-uri=\${IOPC_MQTT_SERVER_URI:tcp://127.0.0.1:1883}
20+
mqtt.server-uri=\${IOPC_MQTT_SERVER_URI:}
2021
mqtt.username=\${IOPC_MQTT_USERNAME:mylogin}
2122
mqtt.password=\${IOPC_MQTT_PASSWORD:mypassword}
2223

Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<browserconfig>
3+
<msapplication>
4+
<tile>
5+
<square150x150logo src="/icon/mstile-150x150.png"/>
6+
<TileColor>#da532c</TileColor>
7+
</tile>
8+
</msapplication>
9+
</browserconfig>
Loading
Loading
Binary file not shown.
Loading
Loading

0 commit comments

Comments
 (0)