]> git.basschouten.com Git - openhab-addons.git/commitdiff
[XMLTV] Preparing for Crowdin and code refining. (#11594)
authorGaël L'hopital <gael@lhopital.org>
Sat, 20 Nov 2021 17:48:03 +0000 (18:48 +0100)
committerGitHub <noreply@github.com>
Sat, 20 Nov 2021 17:48:03 +0000 (18:48 +0100)
* 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>
bundles/org.openhab.binding.xmltv/README.md
bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/XmlTVBindingConstants.java
bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/XmlTVHandlerFactory.java
bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/configuration/XmlChannelConfiguration.java
bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/configuration/XmlTVConfiguration.java
bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/discovery/XmlTVDiscoveryService.java
bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/handler/ChannelHandler.java
bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/handler/XmlTVHandler.java
bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/jaxb/ObjectFactory.java [deleted file]
bundles/org.openhab.binding.xmltv/src/main/resources/OH-INF/i18n/xmltv.properties [new file with mode: 0644]

index b7e6d308a3ade5d98b6e4e2fe2a6d9855683ca64..8c04268aed8d0fd712fadc4fc66beb6ae6c89767 100644 (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.
 
-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.
 XmlTV channels are called Media Channels in this binding in order to avoid messing with openHAB Channels.
index c9f60e8cfddead7d6aa8d9b59dd0c1e1581f43cb..ffe02e01be2beb639bd199a68ec6bb3bf34a71f7 100644 (file)
  */
 package org.openhab.binding.xmltv.internal;
 
-import java.util.Collections;
 import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 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");
 
     // Channel groups
-    public static final String GROUP_CURRENT_PROGRAMME = "currentprog";
     public static final String GROUP_NEXT_PROGRAMME = "nextprog";
     public static final String GROUP_CHANNEL_PROPERTIES = "channelprops";
 
@@ -56,6 +52,6 @@ public class XmlTVBindingConstants {
     public static final String CHANNEL_PROGRAMME_TIMELEFT = "timeLeft";
 
     // Supported Thing types
-    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
-            .unmodifiableSet(Stream.of(XMLTV_FILE_BRIDGE_TYPE, XMLTV_CHANNEL_THING_TYPE).collect(Collectors.toSet()));
+    public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(XMLTV_FILE_BRIDGE_TYPE,
+            XMLTV_CHANNEL_THING_TYPE);
 }
index d88bef01c9eaed1f8ced08b24e38e22d58b2bdd8..e39f90879753c1802bd49ba34fd35eafa8f02229 100644 (file)
@@ -14,19 +14,18 @@ package org.openhab.binding.xmltv.internal;
 
 import static org.openhab.binding.xmltv.internal.XmlTVBindingConstants.*;
 
-import java.util.HashMap;
-import java.util.Hashtable;
-import java.util.Map;
-
+import javax.xml.bind.JAXBContext;
 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.Nullable;
-import org.openhab.binding.xmltv.internal.discovery.XmlTVDiscoveryService;
 import org.openhab.binding.xmltv.internal.handler.ChannelHandler;
 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.discovery.DiscoveryService;
+import org.openhab.core.i18n.TimeZoneProvider;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.Thing;
 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.ThingHandler;
 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.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.osgi.service.component.annotations.Reference;
 
 /**
  * The {@link XmlTVHandlerFactory} is responsible for creating things and thing
@@ -48,8 +46,16 @@ import org.slf4j.LoggerFactory;
 @NonNullByDefault
 @Component(configurationPid = "binding.xmltv", service = ThingHandlerFactory.class)
 public class XmlTVHandlerFactory extends BaseThingHandlerFactory {
-    private final Logger logger = LoggerFactory.getLogger(XmlTVHandlerFactory.class);
-    private final Map<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new HashMap<>();
+    private final XMLInputFactory xif = XMLInputFactory.newFactory();
+    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
     public boolean supportsThingType(ThingTypeUID thingTypeUID) {
@@ -79,37 +85,11 @@ public class XmlTVHandlerFactory extends BaseThingHandlerFactory {
         ThingTypeUID thingTypeUID = thing.getThingTypeUID();
 
         if (XMLTV_FILE_BRIDGE_TYPE.equals(thingTypeUID)) {
-            try {
-                XmlTVHandler bridgeHandler = new XmlTVHandler((Bridge) thing);
-                registerDeviceDiscoveryService(bridgeHandler);
-                return bridgeHandler;
-            } catch (JAXBException e) {
-                logger.error("Unable to create XmlTVHandler : {}", e.getMessage());
-            }
+            return new XmlTVHandler((Bridge) thing, xif, unmarshaller);
         } else if (XMLTV_CHANNEL_THING_TYPE.equals(thingTypeUID)) {
-            return new ChannelHandler(thing);
+            return new ChannelHandler(thing, timeZoneProvider.getTimeZone());
         }
 
         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();
-    }
 }
index c22db5f0d334c5910b273618bb75ff70d2b5a584..b58375dd8824fa00df4bee015f8637b641e2891c 100644 (file)
  */
 package org.openhab.binding.xmltv.internal.configuration;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * The {@link XmlChannelConfiguration} class contains fields mapping
  * Channel thing configuration parameters.
  *
  * @author Gaël L'hopital - Initial contribution
  */
+@NonNullByDefault
 public class XmlChannelConfiguration {
     public static final String CHANNEL_ID = "channelId";
 
-    public String channelId;
-    public Integer offset;
-    public Integer refresh;
+    public String channelId = "";
+    public int offset = 0;
+    public int refresh = 60;
 }
index 9c5a2da0c9c0ee674250aad7a1c78a42b79ab4b7..249c0c230beefe8c7bb457ebc8075ba7aa560b22 100644 (file)
  */
 package org.openhab.binding.xmltv.internal.configuration;
 
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
 /**
  * The {@link XmlTVConfiguration} class contains fields mapping TV bridge
  * configuration parameters.
  *
  * @author Gaël L'hopital - Initial contribution
  */
+@NonNullByDefault
 public class XmlTVConfiguration {
-    public String filePath;
-    public Integer refresh;
-    public String encoding;
+    public String filePath = "";
+    public int refresh = 24;
+    public String encoding = "UTF8";
 }
index 194b85d1c001cd4e23588d13bb8cb27cd57ec85d..3a45e50665c527bf816babcadc9791b1eefde5c1 100644 (file)
  */
 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.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.handler.XmlTVHandler;
-import org.openhab.binding.xmltv.internal.jaxb.Tv;
 import org.openhab.core.config.discovery.AbstractDiscoveryService;
 import org.openhab.core.config.discovery.DiscoveryResult;
 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
 import org.openhab.core.thing.ThingStatus;
 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.LoggerFactory;
 
@@ -33,28 +36,33 @@ import org.slf4j.LoggerFactory;
  *
  * @author Gaël L'hopital - Initial contribution
  */
+@Component(service = ThingHandlerService.class)
 @NonNullByDefault
-public class XmlTVDiscoveryService extends AbstractDiscoveryService {
-    private final Logger logger = LoggerFactory.getLogger(XmlTVDiscoveryService.class);
-
-    private static final int SEARCH_TIME = 10;
+public class XmlTVDiscoveryService extends AbstractDiscoveryService implements ThingHandlerService {
+    private static final int SEARCH_TIME = 5;
 
-    private XmlTVHandler bridgeHandler;
+    private final Logger logger = LoggerFactory.getLogger(XmlTVDiscoveryService.class);
+    private @Nullable XmlTVHandler handler;
 
     /**
      * Creates a XmlTVDiscoveryService with background discovery disabled.
      */
-    public XmlTVDiscoveryService(XmlTVHandler bridgeHandler) {
-        super(XmlTVBindingConstants.SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME);
-        this.bridgeHandler = bridgeHandler;
+    @Activate
+    public XmlTVDiscoveryService() {
+        super(SUPPORTED_THING_TYPES_UIDS, SEARCH_TIME);
+    }
+
+    @Override
+    public void deactivate() {
+        super.deactivate();
     }
 
     @Override
     protected void startScan() {
         logger.debug("Starting XmlTV discovery scan");
-        if (bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
-            Tv tv = bridgeHandler.getXmlFile();
-            if (tv != null) {
+        XmlTVHandler bridgeHandler = handler;
+        if (bridgeHandler != null && bridgeHandler.getThing().getStatus() == ThingStatus.ONLINE) {
+            bridgeHandler.getXmlFile().ifPresent(tv -> {
                 tv.getMediaChannels().stream().forEach(channel -> {
                     String channelId = channel.getId();
                     String uid = channelId.replaceAll("[^A-Za-z0-9_]", "_");
@@ -67,7 +75,19 @@ public class XmlTVDiscoveryService extends AbstractDiscoveryService {
 
                     thingDiscovered(discoveryResult);
                 });
-            }
+            });
         }
     }
+
+    @Override
+    public void setThingHandler(ThingHandler handler) {
+        if (handler instanceof XmlTVHandler) {
+            this.handler = (XmlTVHandler) handler;
+        }
+    }
+
+    @Override
+    public @Nullable ThingHandler getThingHandler() {
+        return handler;
+    }
 }
index 53dfc6640b0c47efb5be22f0eb9787e07fe246db..260ef19a8b4d5a1d3323c1160f16b40b2f7eb167 100644 (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.MediaChannel;
 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.core.io.net.http.HttpUtil;
 import org.openhab.core.library.types.DateTimeType;
@@ -61,14 +60,16 @@ import org.slf4j.LoggerFactory;
 public class ChannelHandler extends BaseThingHandler {
     private final Logger logger = LoggerFactory.getLogger(ChannelHandler.class);
 
-    private @NonNullByDefault({}) ScheduledFuture<?> globalJob;
+    private @Nullable ScheduledFuture<?> globalJob;
     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<>();
+    private final ZoneId zoneId;
 
-    public ChannelHandler(Thing thing) {
+    public ChannelHandler(Thing thing, ZoneId zoneId) {
         super(thing);
+        this.zoneId = zoneId;
     }
 
     @Override
@@ -77,14 +78,14 @@ public class ChannelHandler extends BaseThingHandler {
 
         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(() -> {
                 if (programmes.size() < 2) {
                     refreshProgramList();
                 }
                 if (programmes.isEmpty()) {
-                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE,
-                            "No programmes to come in the current XML file for this channel");
+                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/no-more-programs");
                 } else if (Instant.now().isAfter(programmes.get(0).getProgrammeStop())) {
                     programmes.remove(0);
                 }
@@ -100,8 +101,7 @@ public class ChannelHandler extends BaseThingHandler {
         if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
             XmlTVHandler handler = (XmlTVHandler) bridge.getHandler();
             if (handler != null) {
-                Tv tv = handler.getXmlFile();
-                if (tv != null) {
+                handler.getXmlFile().ifPresentOrElse(tv -> {
                     String channelId = (String) getConfig().get(XmlChannelConfiguration.CHANNEL_ID);
 
                     if (mediaChannel == null) {
@@ -119,9 +119,7 @@ public class ChannelHandler extends BaseThingHandler {
                             .forEach(p -> programmes.add(p));
 
                     updateStatus(ThingStatus.ONLINE);
-                } else {
-                    updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "No file available");
-                }
+                }, () -> updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/no-file-available"));
             } else {
                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
             }
@@ -132,8 +130,9 @@ public class ChannelHandler extends BaseThingHandler {
 
     @Override
     public void dispose() {
-        if (globalJob != null && !globalJob.isCancelled()) {
-            globalJob.cancel(true);
+        ScheduledFuture<?> job = globalJob;
+        if (job != null && !job.isCancelled()) {
+            job.cancel(true);
             globalJob = null;
         }
     }
@@ -153,6 +152,7 @@ public class ChannelHandler extends BaseThingHandler {
      *
      */
     private void updateChannel(ChannelUID channelUID) {
+        // TODO : usage extraction of groupname
         String[] uidElements = channelUID.getId().split("#");
         if (uidElements.length == 2) {
             int target = GROUP_NEXT_PROGRAMME.equals(uidElements[0]) ? 1 : 0;
@@ -161,29 +161,22 @@ public class ChannelHandler extends BaseThingHandler {
 
                 switch (uidElements[1]) {
                     case CHANNEL_ICON:
-                        State icon = null;
-                        if (GROUP_CHANNEL_PROPERTIES.equals(uidElements[0])) {
-                            icon = mediaIcon;
-                        } else {
-                            icon = downloadIcon(programme.getIcons());
-                        }
-                        updateState(channelUID, icon != null ? icon : UnDefType.UNDEF);
+                        State icon = GROUP_CHANNEL_PROPERTIES.equals(uidElements[0]) ? mediaIcon
+                                : downloadIcon(programme.getIcons());
+                        updateState(channelUID, icon);
                         break;
                     case CHANNEL_CHANNEL_URL:
+                        MediaChannel channel = mediaChannel;
                         updateState(channelUID,
-                                mediaChannel != null ? !mediaChannel.getIcons().isEmpty()
-                                        ? new StringType(mediaChannel.getIcons().get(0).getSrc())
+                                channel != null ? !channel.getIcons().isEmpty()
+                                        ? new StringType(channel.getIcons().get(0).getSrc())
                                         : UnDefType.UNDEF : UnDefType.UNDEF);
                         break;
                     case CHANNEL_PROGRAMME_START:
-                        Instant is = programme.getProgrammeStart();
-                        ZonedDateTime zds = ZonedDateTime.ofInstant(is, ZoneId.systemDefault());
-                        updateState(channelUID, new DateTimeType(zds));
+                        updateDateTimeChannel(channelUID, programme.getProgrammeStart());
                         break;
                     case CHANNEL_PROGRAMME_END:
-                        ZonedDateTime zde = ZonedDateTime.ofInstant(programme.getProgrammeStop(),
-                                ZoneId.systemDefault());
-                        updateState(channelUID, new DateTimeType(zde));
+                        updateDateTimeChannel(channelUID, programme.getProgrammeStop());
                         break;
                     case CHANNEL_PROGRAMME_TITLE:
                         List<WithLangType> titles = programme.getTitles();
@@ -212,18 +205,15 @@ public class ChannelHandler extends BaseThingHandler {
                         updateState(channelUID, getDurationInSeconds(Instant.now(), programme.getProgrammeStart()));
                         break;
                     case CHANNEL_PROGRAMME_PROGRESS:
-                        Duration totalLength = Duration.between(programme.getProgrammeStart(),
-                                programme.getProgrammeStop());
-                        Duration elapsed1 = Duration.between(programme.getProgrammeStart(), Instant.now());
-
-                        long secondsElapsed1 = elapsed1.toMillis() / 1000;
-                        long secondsLength = totalLength.toMillis() / 1000;
+                        long totalLength = Duration.between(programme.getProgrammeStart(), programme.getProgrammeStop())
+                                .toSeconds();
+                        long elapsed1 = Duration.between(programme.getProgrammeStart(), Instant.now()).toSeconds();
 
-                        double progress = 100.0 * secondsElapsed1 / secondsLength;
+                        double progress = 100.0 * elapsed1 / totalLength;
                         if (progress > 100 || progress < 0) {
                             logger.debug("Outstanding process");
                         }
-                        updateState(channelUID, new QuantityType<>(progress, Units.PERCENT));
+                        updateState(channelUID, new QuantityType<>((int) progress, Units.PERCENT));
 
                         break;
                 }
@@ -233,17 +223,24 @@ public class ChannelHandler extends BaseThingHandler {
         }
     }
 
+    private void updateDateTimeChannel(ChannelUID channelUID, Instant instant) {
+        ZonedDateTime zds = ZonedDateTime.ofInstant(instant, zoneId);
+        updateState(channelUID, new DateTimeType(zds));
+    }
+
     private QuantityType<?> getDurationInSeconds(Instant from, Instant to) {
-        Duration elapsed = Duration.between(from, to);
-        long secondsElapsed = TimeUnit.MILLISECONDS.toSeconds(elapsed.toMillis());
-        return new QuantityType<>(secondsElapsed, Units.SECOND);
+        long elapsed = Duration.between(from, to).toSeconds();
+        return new QuantityType<>(elapsed, Units.SECOND);
     }
 
-    private @Nullable RawType downloadIcon(List<Icon> icons) {
+    private State downloadIcon(List<Icon> icons) {
         if (!icons.isEmpty()) {
             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;
     }
 }
index 85e7969462259ac7cc95fa24dcc3f322df7c8161..542994e244fe70ebcda5d19fed331caa8993cfda 100644 (file)
@@ -16,12 +16,14 @@ import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.time.Instant;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
-import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
 import javax.xml.bind.Unmarshaller;
 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.Nullable;
 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.Tv;
 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.ThingStatusDetail;
 import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
 import org.openhab.core.types.Command;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -51,16 +55,16 @@ import org.slf4j.LoggerFactory;
 @NonNullByDefault
 public class XmlTVHandler extends BaseBridgeHandler {
     private final Logger logger = LoggerFactory.getLogger(XmlTVHandler.class);
-    private final XMLInputFactory xif = XMLInputFactory.newFactory();
-    private final JAXBContext jc;
+    private final XMLInputFactory xif;
+    private final Unmarshaller unmarshaller;
 
     private @Nullable Tv currentXmlFile;
     private @NonNullByDefault({}) ScheduledFuture<?> reloadJob;
 
-    public XmlTVHandler(Bridge thing) throws JAXBException {
+    public XmlTVHandler(Bridge thing, XMLInputFactory xif, Unmarshaller unmarshaller) {
         super(thing);
-        xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
-        jc = JAXBContext.newInstance(Tv.class);
+        this.xif = xif;
+        this.unmarshaller = unmarshaller;
     }
 
     @Override
@@ -74,9 +78,7 @@ public class XmlTVHandler extends BaseBridgeHandler {
             try {
                 // This can take some seconds depending upon weight of the XmlTV source file
                 xsr = xif.createXMLStreamReader(new FileInputStream(new File(config.filePath)), config.encoding);
-
                 try {
-                    Unmarshaller unmarshaller = jc.createUnmarshaller();
                     Tv xmlFile = (Tv) unmarshaller.unmarshal(xsr);
                     // Remove all finished programmes
                     xmlFile.getProgrammes().removeIf(programme -> Instant.now().isAfter(programme.getProgrammeStop()));
@@ -88,7 +90,7 @@ public class XmlTVHandler extends BaseBridgeHandler {
                         currentXmlFile = xmlFile;
                         updateStatus(ThingStatus.ONLINE);
                     } else {
-                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DISABLED, "XMLTV file seems outdated");
+                        updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.DISABLED, "@text/file-outdated");
                     }
                     xsr.close();
                 } catch (JAXBException e) {
@@ -122,8 +124,12 @@ public class XmlTVHandler extends BaseBridgeHandler {
         // nothing to do
     }
 
-    @Nullable
-    public Tv getXmlFile() {
-        return currentXmlFile;
+    public Optional<Tv> getXmlFile() {
+        return Optional.ofNullable(currentXmlFile);
+    }
+
+    @Override
+    public Collection<Class<? extends ThingHandlerService>> getServices() {
+        return Set.of(XmlTVDiscoveryService.class);
     }
 }
diff --git a/bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/jaxb/ObjectFactory.java b/bundles/org.openhab.binding.xmltv/src/main/java/org/openhab/binding/xmltv/internal/jaxb/ObjectFactory.java
deleted file mode 100644 (file)
index 3fb6f97..0000000
+++ /dev/null
@@ -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();
-    }
-}
diff --git a/bundles/org.openhab.binding.xmltv/src/main/resources/OH-INF/i18n/xmltv.properties b/bundles/org.openhab.binding.xmltv/src/main/resources/OH-INF/i18n/xmltv.properties
new file mode 100644 (file)
index 0000000..9b5f21a
--- /dev/null
@@ -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