[XMLTV] Preparing for Crowdin and code refining. (#11594)

* Preparing for Crowdin and code refining.

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* Satisfying SAT

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* Preventing two potential NPE

Signed-off-by: Gaël L'hopital <gael@lhopital.org>

* Code review comments taken in account

Signed-off-by: clinique <gael@lhopital.org>

* Reverting description removal

Signed-off-by: clinique <gael@lhopital.org>

* Forgot spotless apply

Signed-off-by: clinique <gael@lhopital.org>
This commit is contained in:
Gaël L'hopital 2021-11-20 18:48:03 +01:00 committed by GitHub
parent 4a3a9d5873
commit 2eb0ff5502
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 192 additions and 192 deletions

View File

@ -8,7 +8,7 @@ The building of the XMLTV file itself is taken in charge by so called "grabbers"
Some websites provides updated XMLTV files than can be directly downloaded. Some websites provides updated XMLTV files than can be directly downloaded.
Here is a sample for France : https://www.xmltv.fr/ Here is a sample for France and Switzerland : https://xmltv.ch/
This binding takes an XMLTV file as input and creates a thing for each channel contained in it. This binding takes an XMLTV file as input and creates a thing for each channel contained in it.
XmlTV channels are called Media Channels in this binding in order to avoid messing with openHAB Channels. XmlTV channels are called Media Channels in this binding in order to avoid messing with openHAB Channels.

View File

@ -12,10 +12,7 @@
*/ */
package org.openhab.binding.xmltv.internal; package org.openhab.binding.xmltv.internal;
import java.util.Collections;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
@ -36,7 +33,6 @@ public class XmlTVBindingConstants {
public static final ThingTypeUID XMLTV_CHANNEL_THING_TYPE = new ThingTypeUID(BINDING_ID, "channel"); public static final ThingTypeUID XMLTV_CHANNEL_THING_TYPE = new ThingTypeUID(BINDING_ID, "channel");
// Channel groups // Channel groups
public static final String GROUP_CURRENT_PROGRAMME = "currentprog";
public static final String GROUP_NEXT_PROGRAMME = "nextprog"; public static final String GROUP_NEXT_PROGRAMME = "nextprog";
public static final String GROUP_CHANNEL_PROPERTIES = "channelprops"; public static final String GROUP_CHANNEL_PROPERTIES = "channelprops";
@ -56,6 +52,6 @@ public class XmlTVBindingConstants {
public static final String CHANNEL_PROGRAMME_TIMELEFT = "timeLeft"; public static final String CHANNEL_PROGRAMME_TIMELEFT = "timeLeft";
// Supported Thing types // Supported Thing types
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(XMLTV_FILE_BRIDGE_TYPE,
.unmodifiableSet(Stream.of(XMLTV_FILE_BRIDGE_TYPE, XMLTV_CHANNEL_THING_TYPE).collect(Collectors.toSet())); XMLTV_CHANNEL_THING_TYPE);
} }

View File

@ -14,19 +14,18 @@ package org.openhab.binding.xmltv.internal;
import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.*; import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.*;
import java.util.HashMap; import javax.xml.bind.JAXBContext;
import java.util.Hashtable;
import java.util.Map;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.xmltv.internal.discovery.XmlTVDiscoveryService;
import org.openhab.binding.xmltv.internal.handler.ChannelHandler; import org.openhab.binding.xmltv.internal.handler.ChannelHandler;
import org.openhab.binding.xmltv.internal.handler.XmlTVHandler; import org.openhab.binding.xmltv.internal.handler.XmlTVHandler;
import org.openhab.binding.xmltv.internal.jaxb.Tv;
import org.openhab.core.config.core.Configuration; import org.openhab.core.config.core.Configuration;
import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.i18n.TimeZoneProvider;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing; import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingTypeUID;
@ -34,10 +33,9 @@ import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration; import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger; import org.osgi.service.component.annotations.Reference;
import org.slf4j.LoggerFactory;
/** /**
* The {@link XmlTVHandlerFactory} is responsible for creating things and thing * The {@link XmlTVHandlerFactory} is responsible for creating things and thing
@ -48,8 +46,16 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
@Component(configurationPid = "binding.xmltv", service = ThingHandlerFactory.class) @Component(configurationPid = "binding.xmltv", service = ThingHandlerFactory.class)
public class XmlTVHandlerFactory extends BaseThingHandlerFactory { public class XmlTVHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(XmlTVHandlerFactory.class); private final XMLInputFactory xif = XMLInputFactory.newFactory();
private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>(); private final TimeZoneProvider timeZoneProvider;
private final Unmarshaller unmarshaller;
@Activate
public XmlTVHandlerFactory(final @Reference TimeZoneProvider timeZoneProvider) throws JAXBException {
this.timeZoneProvider = timeZoneProvider;
this.unmarshaller = JAXBContext.newInstance(Tv.class).createUnmarshaller();
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
}
@Override @Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) { public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@ -79,37 +85,11 @@ public class XmlTVHandlerFactory extends BaseThingHandlerFactory {
ThingTypeUID thingTypeUID = thing.getThingTypeUID(); ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (XMLTV_FILE_BRIDGE_TYPE.equals(thingTypeUID)) { if (XMLTV_FILE_BRIDGE_TYPE.equals(thingTypeUID)) {
try { return new XmlTVHandler((Bridge) thing, xif, unmarshaller);
XmlTVHandler bridgeHandler = new XmlTVHandler((Bridge) thing);
registerDeviceDiscoveryService(bridgeHandler);
return bridgeHandler;
} catch (JAXBException e) {
logger.error("Unable to create XmlTVHandler : {}", e.getMessage());
}
} else if (XMLTV_CHANNEL_THING_TYPE.equals(thingTypeUID)) { } else if (XMLTV_CHANNEL_THING_TYPE.equals(thingTypeUID)) {
return new ChannelHandler(thing); return new ChannelHandler(thing, timeZoneProvider.getTimeZone());
} }
return null; return null;
} }
@Override
protected void removeHandler(ThingHandler thingHandler) {
if (thingHandler instanceof XmlTVHandler) {
Thing thing = thingHandler.getThing();
unregisterDeviceDiscoveryService(thing);
}
super.removeHandler(thingHandler);
}
private synchronized void registerDeviceDiscoveryService(XmlTVHandler bridgeHandler) {
XmlTVDiscoveryService discoveryService = new XmlTVDiscoveryService(bridgeHandler);
discoveryServiceRegs.put(bridgeHandler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
private synchronized void unregisterDeviceDiscoveryService(Thing thing) {
ServiceRegistration<?> serviceReg = discoveryServiceRegs.remove(thing.getUID());
serviceReg.unregister();
}
} }

View File

@ -12,16 +12,19 @@
*/ */
package org.openhab.binding.xmltv.internal.configuration; package org.openhab.binding.xmltv.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link XmlChannelConfiguration} class contains fields mapping * The {@link XmlChannelConfiguration} class contains fields mapping
* Channel thing configuration parameters. * Channel thing configuration parameters.
* *
* @author Gaël L'hopital - Initial contribution * @author Gaël L'hopital - Initial contribution
*/ */
@NonNullByDefault
public class XmlChannelConfiguration { public class XmlChannelConfiguration {
public static final String CHANNEL_ID = "channelId"; public static final String CHANNEL_ID = "channelId";
public String channelId; public String channelId = "";
public Integer offset; public int offset = 0;
public Integer refresh; public int refresh = 60;
} }

View File

@ -12,14 +12,17 @@
*/ */
package org.openhab.binding.xmltv.internal.configuration; package org.openhab.binding.xmltv.internal.configuration;
import org.eclipse.jdt.annotation.NonNullByDefault;
/** /**
* The {@link XmlTVConfiguration} class contains fields mapping TV bridge * The {@link XmlTVConfiguration} class contains fields mapping TV bridge
* configuration parameters. * configuration parameters.
* *
* @author Gaël L'hopital - Initial contribution * @author Gaël L'hopital - Initial contribution
*/ */
@NonNullByDefault
public class XmlTVConfiguration { public class XmlTVConfiguration {
public String filePath; public String filePath = "";
public Integer refresh; public int refresh = 24;
public String encoding; public String encoding = "UTF8";
} }

View File

@ -12,18 +12,21 @@
*/ */
package org.openhab.binding.xmltv.internal.discovery; package org.openhab.binding.xmltv.internal.discovery;
import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.XMLTV_CHANNEL_THING_TYPE; import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.*;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.xmltv.internal.XmlTVBindingConstants; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.xmltv.internal.configuration.XmlChannelConfiguration; import org.openhab.binding.xmltv.internal.configuration.XmlChannelConfiguration;
import org.openhab.binding.xmltv.internal.handler.XmlTVHandler; import org.openhab.binding.xmltv.internal.handler.XmlTVHandler;
import org.openhab.binding.xmltv.internal.jaxb.Tv;
import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder; import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -33,28 +36,33 @@ import org.slf4j.LoggerFactory;
* *
* @author Gaël L'hopital - Initial contribution * @author Gaël L'hopital - Initial contribution
*/ */
@Component(service = ThingHandlerService.class)
@NonNullByDefault @NonNullByDefault
public class XmlTVDiscoveryService extends AbstractDiscoveryService { public class XmlTVDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
private static final int SEARCH_TIME = 5;
private final Logger logger = LoggerFactory.getLogger(XmlTVDiscoveryService.class); private final Logger logger = LoggerFactory.getLogger(XmlTVDiscoveryService.class);
private @Nullable XmlTVHandler handler;
private static final int SEARCH_TIME = 10;
private XmlTVHandler bridgeHandler;
/** /**
* Creates a XmlTVDiscoveryService with background discovery disabled. * Creates a XmlTVDiscoveryService with background discovery disabled.
*/ */
public XmlTVDiscoveryService(XmlTVHandler bridgeHandler) { @Activate
super(XmlTVBindingConstants.SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME); public XmlTVDiscoveryService() {
this.bridgeHandler = bridgeHandler; super(SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME);
}
@Override
public void deactivate() {
super.deactivate();
} }
@Override @Override
protected void startScan() { protected void startScan() {
logger.debug("Starting XmlTV discovery scan"); logger.debug("Starting XmlTV discovery scan");
if (bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) { XmlTVHandler bridgeHandler = handler;
Tv tv = bridgeHandler.getXmlFile(); if (bridgeHandler != null && bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
if (tv != null) { bridgeHandler.getXmlFile().ifPresent(tv -> {
tv.getMediaChannels().stream().forEach(channel -> { tv.getMediaChannels().stream().forEach(channel -> {
String channelId = channel.getId(); String channelId = channel.getId();
String uid = channelId.replaceAll("[^A-Za-z0-9_]", "_"); String uid = channelId.replaceAll("[^A-Za-z0-9_]", "_");
@ -67,7 +75,19 @@ public class XmlTVDiscoveryService extends AbstractDiscoveryService {
thingDiscovered(discoveryResult); thingDiscovered(discoveryResult);
}); });
} });
} }
} }
@Override
public void setThingHandler(ThingHandler handler) {
if (handler instanceof XmlTVHandler) {
this.handler = (XmlTVHandler) handler;
}
}
@Override
public @Nullable ThingHandler getThingHandler() {
return handler;
}
} }

View File

@ -30,7 +30,6 @@ import org.openhab.binding.xmltv.internal.configuration.XmlChannelConfiguration;
import org.openhab.binding.xmltv.internal.jaxb.Icon; import org.openhab.binding.xmltv.internal.jaxb.Icon;
import org.openhab.binding.xmltv.internal.jaxb.MediaChannel; import org.openhab.binding.xmltv.internal.jaxb.MediaChannel;
import org.openhab.binding.xmltv.internal.jaxb.Programme; import org.openhab.binding.xmltv.internal.jaxb.Programme;
import org.openhab.binding.xmltv.internal.jaxb.Tv;
import org.openhab.binding.xmltv.internal.jaxb.WithLangType; import org.openhab.binding.xmltv.internal.jaxb.WithLangType;
import org.openhab.core.io.net.http.HttpUtil; import org.openhab.core.io.net.http.HttpUtil;
import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.DateTimeType;
@ -61,14 +60,16 @@ import org.slf4j.LoggerFactory;
public class ChannelHandler extends BaseThingHandler { public class ChannelHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(ChannelHandler.class); private final Logger logger = LoggerFactory.getLogger(ChannelHandler.class);
private @NonNullByDefault({}) ScheduledFuture<?> globalJob; private @Nullable ScheduledFuture<?> globalJob;
private @Nullable MediaChannel mediaChannel; private @Nullable MediaChannel mediaChannel;
private @Nullable RawType mediaIcon = new RawType(new byte[0], RawType.DEFAULT_MIME_TYPE); private State mediaIcon = UnDefType.UNDEF;
public final List<Programme> programmes = new ArrayList<>(); public final List<Programme> programmes = new ArrayList<>();
private final ZoneId zoneId;
public ChannelHandler(Thing thing) { public ChannelHandler(Thing thing, ZoneId zoneId) {
super(thing); super(thing);
this.zoneId = zoneId;
} }
@Override @Override
@ -77,14 +78,14 @@ public class ChannelHandler extends BaseThingHandler {
logger.debug("Initializing Broadcast Channel handler for uid '{}'", getThing().getUID()); logger.debug("Initializing Broadcast Channel handler for uid '{}'", getThing().getUID());
if (globalJob == null || globalJob.isCancelled()) { ScheduledFuture<?> job = globalJob;
if (job == null || job.isCancelled()) {
globalJob = scheduler.scheduleWithFixedDelay(() -> { globalJob = scheduler.scheduleWithFixedDelay(() -> {
if (programmes.size() < 2) { if (programmes.size() < 2) {
refreshProgramList(); refreshProgramList();
} }
if (programmes.isEmpty()) { if (programmes.isEmpty()) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/no-more-programs");
"No programmes to come in the current XML file for this channel");
} else if (Instant.now().isAfter(programmes.get(0).getProgrammeStop())) { } else if (Instant.now().isAfter(programmes.get(0).getProgrammeStop())) {
programmes.remove(0); programmes.remove(0);
} }
@ -100,8 +101,7 @@ public class ChannelHandler extends BaseThingHandler {
if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) { if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
XmlTVHandler handler = (XmlTVHandler) bridge.getHandler(); XmlTVHandler handler = (XmlTVHandler) bridge.getHandler();
if (handler != null) { if (handler != null) {
Tv tv = handler.getXmlFile(); handler.getXmlFile().ifPresentOrElse(tv -> {
if (tv != null) {
String channelId = (String) getConfig().get(XmlChannelConfiguration.CHANNEL_ID); String channelId = (String) getConfig().get(XmlChannelConfiguration.CHANNEL_ID);
if (mediaChannel == null) { if (mediaChannel == null) {
@ -119,9 +119,7 @@ public class ChannelHandler extends BaseThingHandler {
.forEach(p -> programmes.add(p)); .forEach(p -> programmes.add(p));
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
} else { }, () -> updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/no-file-available"));
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "No file available");
}
} else { } else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
} }
@ -132,8 +130,9 @@ public class ChannelHandler extends BaseThingHandler {
@Override @Override
public void dispose() { public void dispose() {
if (globalJob != null && !globalJob.isCancelled()) { ScheduledFuture<?> job = globalJob;
globalJob.cancel(true); if (job != null && !job.isCancelled()) {
job.cancel(true);
globalJob = null; globalJob = null;
} }
} }
@ -153,6 +152,7 @@ public class ChannelHandler extends BaseThingHandler {
* *
*/ */
private void updateChannel(ChannelUID channelUID) { private void updateChannel(ChannelUID channelUID) {
// TODO : usage extraction of groupname
String[] uidElements = channelUID.getId().split("#"); String[] uidElements = channelUID.getId().split("#");
if (uidElements.length == 2) { if (uidElements.length == 2) {
int target = GROUP_NEXT_PROGRAMME.equals(uidElements[0]) ? 1 : 0; int target = GROUP_NEXT_PROGRAMME.equals(uidElements[0]) ? 1 : 0;
@ -161,29 +161,22 @@ public class ChannelHandler extends BaseThingHandler {
switch (uidElements[1]) { switch (uidElements[1]) {
case CHANNEL_ICON: case CHANNEL_ICON:
State icon = null; State icon = GROUP_CHANNEL_PROPERTIES.equals(uidElements[0]) ? mediaIcon
if (GROUP_CHANNEL_PROPERTIES.equals(uidElements[0])) { : downloadIcon(programme.getIcons());
icon = mediaIcon; updateState(channelUID, icon);
} else {
icon = downloadIcon(programme.getIcons());
}
updateState(channelUID, icon != null ? icon : UnDefType.UNDEF);
break; break;
case CHANNEL_CHANNEL_URL: case CHANNEL_CHANNEL_URL:
MediaChannel channel = mediaChannel;
updateState(channelUID, updateState(channelUID,
mediaChannel != null ? !mediaChannel.getIcons().isEmpty() channel != null ? !channel.getIcons().isEmpty()
? new StringType(mediaChannel.getIcons().get(0).getSrc()) ? new StringType(channel.getIcons().get(0).getSrc())
: UnDefType.UNDEF : UnDefType.UNDEF); : UnDefType.UNDEF : UnDefType.UNDEF);
break; break;
case CHANNEL_PROGRAMME_START: case CHANNEL_PROGRAMME_START:
Instant is = programme.getProgrammeStart(); updateDateTimeChannel(channelUID, programme.getProgrammeStart());
ZonedDateTime zds = ZonedDateTime.ofInstant(is, ZoneId.systemDefault());
updateState(channelUID, new DateTimeType(zds));
break; break;
case CHANNEL_PROGRAMME_END: case CHANNEL_PROGRAMME_END:
ZonedDateTime zde = ZonedDateTime.ofInstant(programme.getProgrammeStop(), updateDateTimeChannel(channelUID, programme.getProgrammeStop());
ZoneId.systemDefault());
updateState(channelUID, new DateTimeType(zde));
break; break;
case CHANNEL_PROGRAMME_TITLE: case CHANNEL_PROGRAMME_TITLE:
List<WithLangType> titles = programme.getTitles(); List<WithLangType> titles = programme.getTitles();
@ -212,18 +205,15 @@ public class ChannelHandler extends BaseThingHandler {
updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStart())); updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStart()));
break; break;
case CHANNEL_PROGRAMME_PROGRESS: case CHANNEL_PROGRAMME_PROGRESS:
Duration totalLength = Duration.between(programme.getProgrammeStart(), long totalLength = Duration.between(programme.getProgrammeStart(), programme.getProgrammeStop())
programme.getProgrammeStop()); .toSeconds();
Duration elapsed1 = Duration.between(programme.getProgrammeStart(), Instant.now()); long elapsed1 = Duration.between(programme.getProgrammeStart(), Instant.now()).toSeconds();
long secondsElapsed1 = elapsed1.toMillis() / 1000; double progress = 100.0 * elapsed1 / totalLength;
long secondsLength = totalLength.toMillis() / 1000;
double progress = 100.0 * secondsElapsed1 / secondsLength;
if (progress > 100 || progress < 0) { if (progress > 100 || progress < 0) {
logger.debug("Outstanding process"); logger.debug("Outstanding process");
} }
updateState(channelUID, new QuantityType<>(progress, Units.PERCENT)); updateState(channelUID, new QuantityType<>((int) progress, Units.PERCENT));
break; break;
} }
@ -233,17 +223,24 @@ public class ChannelHandler extends BaseThingHandler {
} }
} }
private QuantityType<?> getDurationInSeconds(Instant from, Instant to) { private void updateDateTimeChannel(ChannelUID channelUID, Instant instant) {
Duration elapsed = Duration.between(from, to); ZonedDateTime zds = ZonedDateTime.ofInstant(instant, zoneId);
long secondsElapsed = TimeUnit.MILLISECONDS.toSeconds(elapsed.toMillis()); updateState(channelUID, new DateTimeType(zds));
return new QuantityType<>(secondsElapsed, Units.SECOND);
} }
private @Nullable RawType downloadIcon(List<Icon> icons) { private QuantityType<?> getDurationInSeconds(Instant from, Instant to) {
long elapsed = Duration.between(from, to).toSeconds();
return new QuantityType<>(elapsed, Units.SECOND);
}
private State downloadIcon(List<Icon> icons) {
if (!icons.isEmpty()) { if (!icons.isEmpty()) {
String url = icons.get(0).getSrc(); String url = icons.get(0).getSrc();
return HttpUtil.downloadImage(url); RawType result = HttpUtil.downloadImage(url);
if (result != null) {
return result;
}
} }
return null; return UnDefType.NULL;
} }
} }

View File

@ -16,12 +16,14 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.time.Instant; import java.time.Instant;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLInputFactory;
@ -31,6 +33,7 @@ import javax.xml.stream.XMLStreamReader;
import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.xmltv.internal.configuration.XmlTVConfiguration; import org.openhab.binding.xmltv.internal.configuration.XmlTVConfiguration;
import org.openhab.binding.xmltv.internal.discovery.XmlTVDiscoveryService;
import org.openhab.binding.xmltv.internal.jaxb.Programme; import org.openhab.binding.xmltv.internal.jaxb.Programme;
import org.openhab.binding.xmltv.internal.jaxb.Tv; import org.openhab.binding.xmltv.internal.jaxb.Tv;
import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Bridge;
@ -38,6 +41,7 @@ import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseBridgeHandler; import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandlerService;
import org.openhab.core.types.Command; import org.openhab.core.types.Command;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -51,16 +55,16 @@ import org.slf4j.LoggerFactory;
@NonNullByDefault @NonNullByDefault
public class XmlTVHandler extends BaseBridgeHandler { public class XmlTVHandler extends BaseBridgeHandler {
private final Logger logger = LoggerFactory.getLogger(XmlTVHandler.class); private final Logger logger = LoggerFactory.getLogger(XmlTVHandler.class);
private final XMLInputFactory xif = XMLInputFactory.newFactory(); private final XMLInputFactory xif;
private final JAXBContext jc; private final Unmarshaller unmarshaller;
private @Nullable Tv currentXmlFile; private @Nullable Tv currentXmlFile;
private @NonNullByDefault({}) ScheduledFuture<?> reloadJob; private @NonNullByDefault({}) ScheduledFuture<?> reloadJob;
public XmlTVHandler(Bridge thing) throws JAXBException { public XmlTVHandler(Bridge thing, XMLInputFactory xif, Unmarshaller unmarshaller) {
super(thing); super(thing);
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); this.xif = xif;
jc = JAXBContext.newInstance(Tv.class); this.unmarshaller = unmarshaller;
} }
@Override @Override
@ -74,9 +78,7 @@ public class XmlTVHandler extends BaseBridgeHandler {
try { try {
// This can take some seconds depending upon weight of the XmlTV source file // This can take some seconds depending upon weight of the XmlTV source file
xsr = xif.createXMLStreamReader(new FileInputStream(new File(config.filePath)), config.encoding); xsr = xif.createXMLStreamReader(new FileInputStream(new File(config.filePath)), config.encoding);
try { try {
Unmarshaller unmarshaller = jc.createUnmarshaller();
Tv xmlFile = (Tv) unmarshaller.unmarshal(xsr); Tv xmlFile = (Tv) unmarshaller.unmarshal(xsr);
// Remove all finished programmes // Remove all finished programmes
xmlFile.getProgrammes().removeIf(programme -> Instant.now().isAfter(programme.getProgrammeStop())); xmlFile.getProgrammes().removeIf(programme -> Instant.now().isAfter(programme.getProgrammeStop()));
@ -88,7 +90,7 @@ public class XmlTVHandler extends BaseBridgeHandler {
currentXmlFile = xmlFile; currentXmlFile = xmlFile;
updateStatus(ThingStatus.ONLINE); updateStatus(ThingStatus.ONLINE);
} else { } else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DISABLED, "XMLTV file seems outdated"); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DISABLED, "@text/file-outdated");
} }
xsr.close(); xsr.close();
} catch (JAXBException e) { } catch (JAXBException e) {
@ -122,8 +124,12 @@ public class XmlTVHandler extends BaseBridgeHandler {
// nothing to do // nothing to do
} }
@Nullable public Optional<Tv> getXmlFile() {
public Tv getXmlFile() { return Optional.ofNullable(currentXmlFile);
return currentXmlFile; }
@Override
public Collection<Class<? extends ThingHandlerService>> getServices() {
return Set.of(XmlTVDiscoveryService.class);
} }
} }

View File

@ -1,69 +0,0 @@
/**
* Copyright (c) 2010-2021 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.binding.xmltv.internal.jaxb;
import javax.xml.bind.annotation.XmlRegistry;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* This object contains factory methods for each Java content
* interface and Java element interface generated in the
* org.openhab.binding.xmltv.internal.jaxb package.
*
* @author Gaël L'hopital - Initial contribution
*/
@XmlRegistry
@NonNullByDefault
public class ObjectFactory {
/**
* Create an instance of {@link Tv }
*
*/
public Tv createTv() {
return new Tv();
}
/**
* Create an instance of {@link Programme }
*
*/
public Programme createProgramme() {
return new Programme();
}
/**
* Create an instance of {@link MediaChannel }
*
*/
public MediaChannel createChannel() {
return new MediaChannel();
}
/**
* Create an instance of {@link Icon }
*
*/
public Icon createIcon() {
return new Icon();
}
/**
* Create an instance of {@link WithLangType }
*
*/
public WithLangType createWithLangType() {
return new WithLangType();
}
}

View File

@ -0,0 +1,64 @@
# binding
binding.xmltv.name = XmlTV Binding
binding.xmltv.description = This is the binding for reading and parsing XmlTV files
# bridge types
thing-type.xmltv.xmltvfile.label = XmlTVFile
thing-type.xmltv.xmltvfile.description = This is the interface to a XmlTV file
thing-type.config.xmltv.xmltvfile.filePath.label = XmlTV File Path
thing-type.config.xmltv.xmltvfile.filePath.description = Path to an XmlTV file.
thing-type.config.xmltv.xmltvfile.refresh.label = Refresh Interval
thing-type.config.xmltv.xmltvfile.refresh.description = Specifies the XMLTV file reload interval in hours.
thing-type.config.xmltv.xmltvfile.encoding.label = File encoding
thing-type.config.xmltv.xmltvfile.encoding.description = Specifies the XMLTV file encoding.
# thing types
thing-type.xmltv.channel.label = Channel
thing-type.xmltv.channel.description = This represent a channel on a given TV file
thing-type.config.xmltv.channel.channelId.label = Channel Id
thing-type.config.xmltv.channel.channelId.description = Id of the channel as presented in the XmlTV file.
thing-type.config.xmltv.channel.offset.label = Offset
thing-type.config.xmltv.channel.offset.description = Moves an event or datetime value forward or backward (in minutes)
thing-type.config.xmltv.channel.refresh.label = Refresh Interval
thing-type.config.xmltv.channel.refresh.description = Specifies the refresh interval in seconds.
# channel group types
channel-group-type.xmltv.channelprops.label = Channel Properties
channel-group-type.xmltv.currentprog.label = Current Program
channel-group-type.xmltv.nextprog.label = Next Program
# channel type
channel-type.xmltv.iconUrl.label = Channel Icon URL
channel-type.xmltv.iconUrl.description = Icon URL of the TV channel.
channel-type.xmltv.progIconUrl.label = Program URL
channel-type.xmltv.progIconUrl.description = URL to an image of the program.
channel-type.xmltv.progTitle.label = Title
channel-type.xmltv.progTitle.description = Program Title.
channel-type.xmltv.progCategory.label = Category
channel-type.xmltv.progCategory.description = Program Category.
channel-type.xmltv.progStart.label = Start Time
channel-type.xmltv.progStart.description = Program Start Time
channel-type.xmltv.progEnd.label = End Time
channel-type.xmltv.progEnd.description = Program End Time
channel-type.xmltv.elapsedTime.label = Current Time
channel-type.xmltv.elapsedTime.description = Current time of currently playing program.
channel-type.xmltv.remainingTime.label = Remaining Time
channel-type.xmltv.remainingTime.description = Time remaining until end of the program.
channel-type.xmltv.timeLeft.label = Time Left
channel-type.xmltv.timeLeft.description = Time left before program start
channel-type.xmltv.progress.label = Progress
channel-type.xmltv.progress.description = Relative progression of the current program.
channel-type.xmltv.icon.label = Icon
channel-type.xmltv.icon.description = Icon of the channel / program.
# messages
no-more-programs = No programmes to come in the current XML file for this channel
no-file-available = No file available
file-outdated = XMLTV file seems outdated