Skip to content

Commit

Permalink
Service to find suggested addons to install (#3806)
Browse files Browse the repository at this point in the history
Signed-off-by: Andrew Fiddian-Green <software@whitebear.ch>
Co-authored-by: Mark Herwege <mark.herwege@telenet.be>
  • Loading branch information
andrewfg and mherwege authored Dec 7, 2023
1 parent de9912d commit 62a50a4
Show file tree
Hide file tree
Showing 31 changed files with 1,730 additions and 3 deletions.
18 changes: 18 additions & 0 deletions bom/openhab-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,24 @@
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.addon</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.addon.mdns</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.addon.upnp</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.mdns</artifactId>
Expand Down
29 changes: 29 additions & 0 deletions bundles/org.openhab.core.config.discovery.addon.mdns/.classpath
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="test" value="true"/>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="annotationpath" value="target/dependency"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="annotationpath" value="target/dependency"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>
23 changes: 23 additions & 0 deletions bundles/org.openhab.core.config.discovery.addon.mdns/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.core.config.discovery.addon.mdns</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>
14 changes: 14 additions & 0 deletions bundles/org.openhab.core.config.discovery.addon.mdns/NOTICE
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
This content is produced and maintained by the openHAB project.

* Project home: https://www.openhab.org

== Declared Project Licenses

This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.

== Source Code

https://github.com/openhab/openhab-core

34 changes: 34 additions & 0 deletions bundles/org.openhab.core.config.discovery.addon.mdns/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.reactor.bundles</artifactId>
<version>4.1.0-SNAPSHOT</version>
</parent>

<artifactId>org.openhab.core.config.discovery.addon.mdns</artifactId>

<name>openHAB Core :: Bundles :: mDNS Suggested Add-on Finder</name>

<dependencies>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.mdns</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.config.discovery.addon</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.openhab.core.bundles</groupId>
<artifactId>org.openhab.core.addon</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/**
* Copyright (c) 2010-2023 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.core.config.discovery.addon.mdns;

import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_NAME_MDNS;
import static org.openhab.core.config.discovery.addon.AddonFinderConstants.SERVICE_TYPE_MDNS;

import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.jmdns.ServiceEvent;
import javax.jmdns.ServiceInfo;
import javax.jmdns.ServiceListener;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.addon.AddonDiscoveryMethod;
import org.openhab.core.addon.AddonInfo;
import org.openhab.core.common.ThreadPoolManager;
import org.openhab.core.config.discovery.addon.AddonFinder;
import org.openhab.core.config.discovery.addon.BaseAddonFinder;
import org.openhab.core.io.transport.mdns.MDNSClient;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* This is a {@link MDNSAddonFinder} for finding suggested add-ons via mDNS.
*
* @author Andrew Fiddian-Green - Initial contribution
* @author Mark Herwege - refactor to allow uninstall
*/
@NonNullByDefault
@Component(service = AddonFinder.class, name = MDNSAddonFinder.SERVICE_NAME)
public class MDNSAddonFinder extends BaseAddonFinder implements ServiceListener {

public static final String SERVICE_TYPE = SERVICE_TYPE_MDNS;
public static final String SERVICE_NAME = SERVICE_NAME_MDNS;

private static final String NAME = "name";
private static final String APPLICATION = "application";

private final Logger logger = LoggerFactory.getLogger(MDNSAddonFinder.class);
private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool(SERVICE_NAME);
private final Map<String, ServiceInfo> services = new ConcurrentHashMap<>();
private MDNSClient mdnsClient;

@Activate
public MDNSAddonFinder(@Reference MDNSClient mdnsClient) {
this.mdnsClient = mdnsClient;
}

/**
* Adds the given mDNS service to the set of discovered services.
*
* @param device the mDNS service to be added.
*/
public void addService(ServiceInfo service, boolean isResolved) {
String qualifiedName = service.getQualifiedName();
if (isResolved || !services.containsKey(qualifiedName)) {
if (services.put(qualifiedName, service) == null) {
logger.trace("Added service: {}", qualifiedName);
}
}
}

@Deactivate
public void deactivate() {
services.clear();
unsetAddonCandidates();
}

@Override
public void setAddonCandidates(List<AddonInfo> candidates) {
// Remove listeners for all service types that are no longer in candidates
addonCandidates.stream().filter(c -> !candidates.contains(c))
.forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType()))
.filter(m -> !m.getMdnsServiceType().isEmpty())
.forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this)));

// Add listeners for all service types in candidates
super.setAddonCandidates(candidates);
addonCandidates
.forEach(c -> c.getDiscoveryMethods().stream().filter(m -> SERVICE_TYPE.equals(m.getServiceType()))
.filter(m -> !m.getMdnsServiceType().isEmpty()).forEach(m -> {
String serviceType = m.getMdnsServiceType();
mdnsClient.addServiceListener(serviceType, this);
scheduler.submit(() -> mdnsClient.list(serviceType));
}));
}

@Override
public void unsetAddonCandidates() {
addonCandidates.forEach(c -> c.getDiscoveryMethods().stream()
.filter(m -> SERVICE_TYPE.equals(m.getServiceType())).filter(m -> !m.getMdnsServiceType().isEmpty())
.forEach(m -> mdnsClient.removeServiceListener(m.getMdnsServiceType(), this)));
super.unsetAddonCandidates();
}

@Override
public Set<AddonInfo> getSuggestedAddons() {
Set<AddonInfo> result = new HashSet<>();
for (AddonInfo candidate : addonCandidates) {
for (AddonDiscoveryMethod method : candidate.getDiscoveryMethods().stream()
.filter(method -> SERVICE_TYPE.equals(method.getServiceType())).toList()) {
Map<String, Pattern> matchProperties = method.getMatchProperties().stream()
.collect(Collectors.toMap(property -> property.getName(), property -> property.getPattern()));

Set<String> matchPropertyKeys = matchProperties.keySet().stream()
.filter(property -> (!NAME.equals(property) && !APPLICATION.equals(property)))
.collect(Collectors.toSet());

logger.trace("Checking candidate: {}", candidate.getUID());
for (ServiceInfo service : services.values()) {

logger.trace("Checking service: {}/{}", service.getQualifiedName(), service.getNiceTextString());
if (method.getMdnsServiceType().equals(service.getType())
&& propertyMatches(matchProperties, NAME, service.getName())
&& propertyMatches(matchProperties, APPLICATION, service.getApplication())
&& matchPropertyKeys.stream().allMatch(
name -> propertyMatches(matchProperties, name, service.getPropertyString(name)))) {
result.add(candidate);
logger.debug("Suggested add-on found: {}", candidate.getUID());
break;
}
}
}
}
return result;
}

@Override
public String getServiceName() {
return SERVICE_NAME;
}

/*
* ************ MDNSClient call-back methods ************
*/

@Override
public void serviceAdded(@Nullable ServiceEvent event) {
if (event != null) {
ServiceInfo service = event.getInfo();
if (service != null) {
addService(service, false);
}
}
}

@Override
public void serviceRemoved(@Nullable ServiceEvent event) {
}

@Override
public void serviceResolved(@Nullable ServiceEvent event) {
if (event != null) {
ServiceInfo service = event.getInfo();
if (service != null) {
addService(service, true);
}
}
}
}
Loading

0 comments on commit 62a50a4

Please sign in to comment.