added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,38 @@
<?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 excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.kodi</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.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
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-addons

View File

@@ -0,0 +1,238 @@
# Kodi Binding
[Kodi](https://kodi.tv) (formerly known as XBMC) is an free and open source (GPL) software media center for playing videos, music, pictures, games, and more.
Kodi runs on Linux, OS X, BSD, Windows, iOS, and Android.
It allows users to play and view most videos, music, podcasts, and other digital media files from local and network storage media and the internet.
The Kodi Binding integrates Kodi media center support with openHAB, allowing both controlling the player as well as retrieving player status data like the currently played movie title.
The Kodi binding is the successor to the openHAB 1.x xbmc binding.
## Preparation
In order to allow Kodi to be controlled through this binding, you need to enable the Kodi application remote control feature.
Please enable "Allow remote control from applications on this/other systems" in the Kodi settings menu under:
* Settings ➔ Services ➔ Control ➔
* Allow remote control from applications on **this** systems
* Allow remote control from applications on **other** systems
To make use of the auto-discovery feature, you additionally need to enable "Allow control of Kodi via UPnP" in the Kodi settings menu.
* Settings ➔ Services ➔ UPnP / DLNA ➔ Allow remote control via UPnP
## Supported Things
This binding provides only one thing type: The Kodi media center.
Create one Kodi thing per Kodi instance available in your home automation system.
All Kodi devices are registered as an audio sink in openHAB.
## Discovery
The binding supports auto-discovery for available and prepared (see above) instances of the Kodi media center on your local network.
Auto-discovery is enabled by default.
To disable it, you can add the following line to `<openHAB-conf>/services/runtime.cfg`:
```
discovery.kodi:background=false
```
## Binding Configuration
The following configuration options are available for the Kodi binding:
| Parameter | Name | Description | Required |
|---------------|--------------|----------------------------------------------------------------------------|----------|
| `callbackUrl` | Callback URL | URL to use for playing notification sounds, e.g. `http://192.168.0.2:8080` | no |
### Thing Configuration
The Kodi thing requires the IP address of the device hosting your Kodi media center instance, the TCP port to access it (default: `9090`) and the HTTP port to build URLs to the Kodi webinterface for downloading thumbnail and fanart images (default: `8080`).
You optionally can define a `httpUser` and a `httpPassword` parameter if the access to your Kodi webinterface is protected.
The IP address will be found by the auto-discovery feature.
You can use the `notificationVolume` property for setting a default volume (in %) as well as a `notificationTimeout` (in s) to be used to play notifications.
A manual setup through a `things/kodi.things` file could look like this:
```
Thing kodi:kodi:myKodi "Kodi" @ "Living Room" [ipAddress="192.168.1.100", port=9090, httpPort=8080]
```
## Channels
The Kodi thing supports the following channels:
| Channel Type ID | Item Type | Description |
|-----------------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| mute | Switch | Mute/unmute your playback |
| volume | Dimmer | Read or control the volume of your playback |
| control | Player | Control the Kodi player, e.g. `PLAY`, `PAUSE`, `NEXT`, `PREVIOUS`, `FASTFORWARD`, `REWIND` |
| stop | Switch | Write `ON` to this channel: Stops the Kodi player. If this channel is `ON`, the player is stopped, otherwise Kodi is in another state (see control channel) |
| title | String | Title of the currently played song/movie/tv episode |
| originaltitle | String | Title of the currently played song/movie/tv episode in local language |
| showtitle | String | Title of the currently played tv-show; empty for other types |
| album | String | Album name of the currently played song |
| artist | String | Artist name of the currently played song or director of the currently played movie |
| playuri | String | Plays the file with the provided URI. The URI can be amended by `#timestamp=<s>` where `s` is position in seconds at which the video should be started. |
| playfavorite | String | Plays or opens the favorite with the provided title (channel's state options contains available favorites) |
| pvr-open-tv | String | Opens the PVR TV channel with the provided name (channel's state options contains available PVR TV channels) |
| pvr-open-radio | String | Opens the PVR Radio channel with the provided name (channel's state options contains available PVR Radio channels) |
| pvr-channel | String | Title of the currently played PVR channel |
| shownotification | String | Shows the provided notification message on the screen |
| input | String | Sends a key stroke to Kodi to navigate in the UI. Valid commands are: `Back`, `ContextMenu`, `Down`, `Home`, `Info`, `Left`, `Right`, `Select`, `ShowCodec`, `ShowOSD`, `ShowPlayerProcessInfo` and `Up`. `ExecuteAction` and `SendText` should be used with the dedicated channels `inputaction` and `inputtext`. |
| inputtext | String | Sends a generic input (unicode) text to Kodi. |
| inputaction | String | Sends a predefined action to Kodi to control the UI and/or perform other tasks. Valid commands are: `left`, `right`, `up`, `down`, `pageup`, `pagedown`, `select`, `highlight`, `parentdir`, `parentfolder`, `back`, `menu`, `previousmenu`, `info`, `pause`, `stop`, `skipnext`, `skipprevious`, `fullscreen`, `aspectratio`, `stepforward`, `stepback`, `bigstepforward`, `bigstepback`, `chapterorbigstepforward`, `chapterorbigstepback`, `osd`, `showsubtitles`, `nextsubtitle`, `cyclesubtitle`, `playerdebug`, `codecinfo`, `playerprocessinfo`, `nextpicture`, `previouspicture`, `zoomout`, `zoomin`, `playlist`, `queue`, `zoomnormal`, `zoomlevel1`, `zoomlevel2`, `zoomlevel3`, `zoomlevel4`, `zoomlevel5`, `zoomlevel6`, `zoomlevel7`, `zoomlevel8`, `zoomlevel9`, `nextcalibration`, `resetcalibration`, `analogmove`, `analogmovex`, `analogmovey`, `rotate`, `rotateccw`, `close`, `subtitledelayminus`, `subtitledelay`, `subtitledelayplus`, `audiodelayminus`, `audiodelay`, `audiodelayplus`, `subtitleshiftup`, `subtitleshiftdown`, `subtitlealign`, `audionextlanguage`, `verticalshiftup`, `verticalshiftdown`, `nextresolution`, `audiotoggledigital`, `number0`, `number1`, `number2`, `number3`, `number4`, `number5`, `number6`, `number7`, `number8`, `number9`, `smallstepback`, `fastforward`, `rewind`, `play`, `playpause`, `switchplayer`, `delete`, `copy`, `move`, `screenshot`, `rename`, `togglewatched`, `scanitem`, `reloadkeymaps`, `volumeup`, `volumedown`, `mute`, `backspace`, `scrollup`, `scrolldown`, `analogfastforward`, `analogrewind`, `moveitemup`, `moveitemdown`, `contextmenu`, `shift`, `symbols`, `cursorleft`, `cursorright`, `showtime`, `analogseekforward`, `analogseekback`, `showpreset`, `nextpreset`, `previouspreset`, `lockpreset`, `randompreset`, `increasevisrating`, `decreasevisrating`, `showvideomenu`, `enter`, `increaserating`, `decreaserating`, `setrating`, `togglefullscreen`, `nextscene`, `previousscene`, `nextletter`, `prevletter`, `jumpsms2`, `jumpsms3`, `jumpsms4`, `jumpsms5`, `jumpsms6`, `jumpsms7`, `jumpsms8`, `jumpsms9`, `filter`, `filterclear`, `filtersms2`, `filtersms3`, `filtersms4`, `filtersms5`, `filtersms6`, `filtersms7`, `filtersms8`, `filtersms9`, `firstpage`, `lastpage`, `guiprofile`, `red`, `green`, `yellow`, `blue`, `increasepar`, `decreasepar`, `volampup`, `volampdown`, `volumeamplification`, `createbookmark`, `createepisodebookmark`, `settingsreset`, `settingslevelchange`, `stereomode`, `nextstereomode`, `previousstereomode`, `togglestereomode`, `stereomodetomono`, `channelup`, `channeldown`, `previouschannelgroup`, `nextchannelgroup`, `playpvr`, `playpvrtv`, `playpvrradio`, `record`, `togglecommskip`, `showtimerrule`, `leftclick`, `rightclick`, `middleclick`, `doubleclick`, `longclick`, `wheelup`, `wheeldown`, `mousedrag`, `mousemove`, `tap`, `longpress`, `pangesture`, `zoomgesture`, `rotategesture`, `swipeleft`, `swiperight`, `swipeup`, `swipedown`, `error`, `noop`. |
| systemcommand | String | This channel allows to send system commands to `Shutdown`, `Suspend`, `Hibernate`, `Reboot` or `Quit` Kodi (channel's state options contains available system commands) |
| mediatype | String | The media type of the current file. Valid return values are: `unknown`, `channel`, `episode`, `movie`, `musicvideo`, `picture`, `radio`, `song`, `video` |
| mediaid | Number | The media_id in database of Kodi |
| mediafile | String | Full path and file name of the current file |
| uniqueid-imdb | String | IMDB link ID of currently playing media **(Advanced)** |
| uniqueid-tmdb | String | TheMovieDB link ID of currently playing media **(Advanced)** |
| uniqueid-douban | String | Douban link ID of currently playing media **(Advanced)** |
| uniqueid-imdbtvshow | String | IMDB link ID of the season of currently playing tv episode **(Advanced)** |
| uniqueid-tmdbtvshow | String | IMDB link ID of the season of currently playing tv episode **(Advanced)** |
| uniqueid-tmdbepisode | String | IMDB link ID of single episode of currently playing tv episode **(Advanced)** |
| season | Number | Season number if currently playing tv episode |
| episode | Number | Episode number if currently playing tv episode |
| genreList | String | A comma-separated list of genres of the current file |
| thumbnail | Image | The URL to the thumbnail of the current file |
| fanart | Image | The URL to the fanart of the current file |
| playnotification | String | Plays the notification sound by a given URI |
| subtitle-enabled | Switch | Display/hidden subtitle |
| subtitle-index | Number | Set or get subtitle index of currently playing media |
| subtitle-language | String | Display subtitle language of currently playing media |
| subtitle-name | String | Display subtitle title of currently playing media |
| audio-index | Number | Audio stream index of currently playing media |
| audio-codec | String | Audio codec of currently playing media **(Advanced)** |
| audio-language | String | Display language of currently playing audio stream **(Advanced)** |
| audio-name | String | Display title of currently playing audio stream **(Advanced)** |
| audio-channels | Number | Display channels of currently playing audio stream **(Advanced)** |
| video-codec | String | Video codec of currently playing media **(Advanced)** |
| video-index | Number | Index of currently playing multi stream video **(Advanced)** |
| video-height | Number | Height of currently playing video **(Advanced)** |
| video-weight | Number | Width of currently playing video **(Advanced)** |
| currenttime | Number:Time | Current time of currently playing media |
| currenttimepercentage | Number:Dimensionless | Current time of currently playing media |
| duration | Number:Time | Length of currently playing media |
| rating | Number | rating of currently playing media **(Advanced)** |
| mpaa | String | MPAA rating of currently playing media **(Advanced)** |
| userrating | Number | personal rating of currently playing media **(Advanced)** |
| profile | String | Current profile **(Advanced)** |
Kodi things are extensible by channels of type `shownotification`, so that different notification pop-ups can be configured for different use cases.
### Channel Configuration
**group** The PVR channels can be put into user-defined PVR channel groups.
There are two default PVR channel groups.
One for PVR TV channels and one for PVR radio channels.
The default labels are "All channels" (in German systems "Alle Kanäle").
You have to adjust this configuration to use the `pvr-open-tv` and `pvr-open-radio` channels properly.
You can optionally configure an user-defined PVR channel group.
The PVR channels from Kodi will be populated during runtime into the state options of the `pvr-open-tv` and `pvr-open-radio` channels.
**shownotification** The `shownotification` channel type has three configuration parameters through which it can be customized:
| Parameter | Type | Default | Description |
|-------------|---------|---------|------------------------------------------------------------------------|
| title | Text | openHAB | Title of the notification |
| displayTime | Integer | 5000 | Time the notification is shown (in ms) |
| icon | Text | | Icon to use (e.g. "alarm"). One of the openHAB icons (as listed [here](https://www.openhab.org/docs/configuration/iconsets/classic/)). |
## Audio Support
All Kodi instances are registered as an audio sink in the framework.
Audio streams are sent to the `playnotification` channel and will change the volume of the audio sink using the `notificationVolume` defined in the properties for the thing, not the `volume`.
URL audio streams (e.g. an Internet radio stream) are an exception and do not get sent to the `playnotification` channel.
Instead, these will be sent to the `playuri` channel.
## Full Example
A manual setup through a `things/kodi.things` file could look like this:
```java
Thing kodi:kodi:myKodi "Kodi" @ "Living Room" [ipAddress="192.168.1.100", port=9090] {
Channels:
Type pvr-open-tv : pvr-open-tv [
group="All channels"
]
Type shownotification : error-notification [
title="openHAB - Error",
icon="error"
]
Type shownotification : weather-notification [
title="openHAB - Weather",
icon="sun_clouds"
]
}
```
### Item Configuration
demo.items
```java
Switch myKodi_mute "Mute" { channel="kodi:kodi:myKodi:mute" }
Dimmer myKodi_volume "Volume [%d]" { channel="kodi:kodi:myKodi:volume" }
Player myKodi_control "Control" { channel="kodi:kodi:myKodi:control" }
Switch myKodi_stop "Stop" { channel="kodi:kodi:myKodi:stop" }
String myKodi_title "Title [%s]" { channel="kodi:kodi:myKodi:title" }
String myKodi_showtitle "Show title [%s]" { channel="kodi:kodi:myKodi:showtitle" }
String myKodi_album "Album [%s]" { channel="kodi:kodi:myKodi:album" }
String myKodi_artist "Artist [%s]" { channel="kodi:kodi:myKodi:artist" }
String myKodi_playuri "Play URI" { channel="kodi:kodi:myKodi:playuri" }
String myKodi_playfavorite "Play favorite" { channel="kodi:kodi:myKodi:playfavorite" }
String myKodi_pvropentv "Play PVR TV channel" { channel="kodi:kodi:myKodi:pvr-open-tv" }
String myKodi_pvropenradio "Play PVR Radio channel" { channel="kodi:kodi:myKodi:pvr-open-radio" }
String myKodi_pvrchannel "PVR channel [%s]" { channel="kodi:kodi:myKodi:pvr-channel" }
String myKodi_notification "Notification" { channel="kodi:kodi:myKodi:shownotification" }
String myKodi_error-notification "Error Notification" { channel="kodi:kodi:myKodi:error-notification" }
String myKodi_weather-notification "Weather Notification" { channel="kodi:kodi:myKodi:weather-notification" }
String myKodi_input "Input" { channel="kodi:kodi:myKodi:input" }
String myKodi_inputtext "Inputtext" { channel="kodi:kodi:myKodi:inputtext" }
String myKodi_systemcommand "Systemcommand" { channel="kodi:kodi:myKodi:systemcommand" }
String myKodi_mediatype "Mediatype [%s]" { channel="kodi:kodi:myKodi:mediatype" }
String myKodi_genrelist "Genres [%s]" { channel="kodi:kodi:myKodi:genreList" }
Image myKodi_thumbnail { channel="kodi:kodi:myKodi:thumbnail" }
Image myKodi_fanart { channel="kodi:kodi:myKodi:fanart" }
Number:Time myKodi_currenttime "Current Time [%d %unit%]" { channel="kodi:kodi:myKodi:currenttime" }
Number:Dimensionless myKodi_ctp "Current Time [%d %unit%]" { channel="kodi:kodi:myKodi:currenttimepercentage" }
Number:Time myKodi_duration "Duration [%d %unit%]" { channel="kodi:kodi:myKodi:duration" }
String myKodi_playnotification "Play notification URI" { channel="kodi:kodi:myKodi:playnotification" }
```
### Sitemap Configuration
demo.sitemap
```perl
sitemap demo label="myKodi"
{
Frame label="myKodi" {
Switch item=myKodi_mute
Slider item=myKodi_volume
Selection item=myKodi_control mappings=[PLAY='Play', PAUSE='Pause', NEXT='Next', PREVIOUS='Previous', FASTFORWARD='Fastforward', REWIND='Rewind']
Default item=myKodi_control
Switch item=myKodi_stop
Text item=myKodi_title
Text item=myKodi_showtitle
Text item=myKodi_album
Text item=myKodi_artist
Selection item=myKodi_pvropentv
Selection item=myKodi_pvropenchannel
Text item=myKodi_pvrchannel
Selection item=myKodi_input mappings=[Up='Up', Down='Down', Left='Left', Right='Right', Select='Select', Back='Back', Home='Home', ContextMenu='ContextMenu', Info='Info']
Selection item=myKodi_systemcommand
Text item=myKodi_mediatype
Text item=myKodi_genrelist
Image item=myKodi_thumbnail
Image item=myKodi_fanart
Text item=myKodi_currenttime
Text item=myKodi_ctp
Text item=myKodi_duration
}
}
```

View File

@@ -0,0 +1,17 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.kodi</artifactId>
<name>openHAB Add-ons :: Bundles :: Kodi Binding</name>
</project>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.kodi-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-kodi" description="Kodi Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<feature>openhab-transport-http</feature>
<feature>openhab-transport-upnp</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.kodi/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,129 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.kodi.internal.handler.KodiHandler;
import org.openhab.core.audio.AudioFormat;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSink;
import org.openhab.core.audio.AudioStream;
import org.openhab.core.audio.FixedLengthAudioStream;
import org.openhab.core.audio.URLAudioStream;
import org.openhab.core.audio.UnsupportedAudioFormatException;
import org.openhab.core.audio.UnsupportedAudioStreamException;
import org.openhab.core.library.types.PercentType;
import org.openhab.core.library.types.StringType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This makes Kodi to serve as an {@link AudioSink}.
*
* @author Kai Kreuzer - Initial contribution and API
* @author Paul Frank - Adapted for Kodi
* @author Christoph Weitkamp - Improvements for playing audio notifications
*/
public class KodiAudioSink implements AudioSink {
private final Logger logger = LoggerFactory.getLogger(KodiAudioSink.class);
private static final Set<AudioFormat> SUPPORTED_AUDIO_FORMATS = Collections
.unmodifiableSet(Stream.of(AudioFormat.MP3, AudioFormat.WAV).collect(Collectors.toSet()));
private static final Set<Class<? extends AudioStream>> SUPPORTED_AUDIO_STREAMS = Collections
.unmodifiableSet(Stream.of(FixedLengthAudioStream.class, URLAudioStream.class).collect(Collectors.toSet()));
// Needed because Kodi does multiple requests for the stream
private static final int STREAM_TIMEOUT = 30;
private final KodiHandler handler;
private final AudioHTTPServer audioHTTPServer;
private final String callbackUrl;
public KodiAudioSink(KodiHandler handler, AudioHTTPServer audioHTTPServer, String callbackUrl) {
this.handler = handler;
this.audioHTTPServer = audioHTTPServer;
this.callbackUrl = callbackUrl;
}
@Override
public String getId() {
return handler.getThing().getUID().toString();
}
@Override
public String getLabel(Locale locale) {
return handler.getThing().getLabel();
}
@Override
public void process(AudioStream audioStream)
throws UnsupportedAudioFormatException, UnsupportedAudioStreamException {
if (audioStream == null) {
// in case the audioStream is null, this should be interpreted as a request to end any currently playing
// stream.
logger.trace("Stop currently playing stream.");
handler.stop();
} else {
AudioFormat format = audioStream.getFormat();
if (!AudioFormat.MP3.isCompatible(format) && !AudioFormat.WAV.isCompatible(format)) {
throw new UnsupportedAudioFormatException("Currently only MP3 and WAV formats are supported.", format);
}
if (audioStream instanceof URLAudioStream) {
// it is an external URL, the speaker can access it itself and play it
String url = ((URLAudioStream) audioStream).getURL();
logger.trace("Processing audioStream URL {} of format {}.", url, format);
handler.playURI(new StringType(url));
} else if (audioStream instanceof FixedLengthAudioStream) {
if (callbackUrl != null) {
// we serve it on our own HTTP server for 30 seconds as Kodi requests the stream several times
// Form the URL for streaming the notification from the OH2 web server
String url = callbackUrl
+ audioHTTPServer.serve((FixedLengthAudioStream) audioStream, STREAM_TIMEOUT);
logger.trace("Processing audioStream URL {} of format {}.", url, format);
handler.playNotificationSoundURI(new StringType(url));
} else {
logger.warn("We do not have any callback url, so Kodi cannot play the audio stream!");
}
} else {
throw new UnsupportedAudioStreamException(
"Kodi can only handle URLAudioStream or FixedLengthAudioStreams.", audioStream.getClass());
}
}
}
@Override
public Set<AudioFormat> getSupportedFormats() {
return SUPPORTED_AUDIO_FORMATS;
}
@Override
public Set<Class<? extends AudioStream>> getSupportedStreams() {
return SUPPORTED_AUDIO_STREAMS;
}
@Override
public PercentType getVolume() {
return handler.getVolume();
}
@Override
public void setVolume(PercentType volume) {
handler.setVolume(volume);
}
}

View File

@@ -0,0 +1,122 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link KodiBinding} class defines common constants, which are used across
* the whole binding.
*
* @author Paul Frank - Initial contribution
* @author Christoph Weitkamp - Added channels for opening PVR TV or Radio streams
* @author Andreas Reinhardt & Christoph Weitkamp - Added channels for thumbnail and fanart
* @author Christoph Weitkamp - Improvements for playing audio notifications
*/
@NonNullByDefault
public class KodiBindingConstants {
public static final String BINDING_ID = "kodi";
// List of all Thing Type UIDs
public static final ThingTypeUID THING_TYPE_KODI = new ThingTypeUID(BINDING_ID, "kodi");
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_KODI);
// List of thing parameters names
public static final String HOST_PARAMETER = "ipAddress";
public static final String WS_PORT_PARAMETER = "port";
public static final String HTTP_PORT_PARAMETER = "httpPort";
public static final String HTTP_USER_PARAMETER = "httpUser";
public static final String HTTP_PASSWORD_PARAMETER = "httpPassword";
public static final String REFRESH_PARAMETER = "refreshInterval";
// List of all Channel ids
public static final String CHANNEL_MUTE = "mute";
public static final String CHANNEL_VOLUME = "volume";
public static final String CHANNEL_STOP = "stop";
public static final String CHANNEL_CONTROL = "control";
public static final String CHANNEL_PLAYURI = "playuri";
public static final String CHANNEL_PLAYFAVORITE = "playfavorite";
public static final String CHANNEL_PVR_OPEN_TV = "pvr-open-tv";
public static final String CHANNEL_PVR_OPEN_RADIO = "pvr-open-radio";
public static final String CHANNEL_SHOWNOTIFICATION = "shownotification";
public static final String CHANNEL_PLAYNOTIFICATION = "playnotification";
public static final String CHANNEL_PROFILE = "profile";
public static final String CHANNEL_INPUT = "input";
public static final String CHANNEL_INPUTTEXT = "inputtext";
public static final String CHANNEL_INPUTACTION = "inputaction";
public static final String CHANNEL_SYSTEMCOMMAND = "systemcommand";
public static final String CHANNEL_ARTIST = "artist";
public static final String CHANNEL_TITLE = "title";
public static final String CHANNEL_ORIGINALTITLE = "originaltitle";
public static final String CHANNEL_SHOWTITLE = "showtitle";
public static final String CHANNEL_ALBUM = "album";
public static final String CHANNEL_MEDIATYPE = "mediatype";
public static final String CHANNEL_MEDIAID = "mediaid";
public static final String CHANNEL_MEDIAFILE = "mediafile";
public static final String CHANNEL_GENRELIST = "genreList";
public static final String CHANNEL_PVR_CHANNEL = "pvr-channel";
public static final String CHANNEL_THUMBNAIL = "thumbnail";
public static final String CHANNEL_FANART = "fanart";
public static final String CHANNEL_AUDIO_CODEC = "audio-codec";
public static final String CHANNEL_AUDIO_CHANNELS = "audio-channels";
public static final String CHANNEL_AUDIO_INDEX = "audio-index";
public static final String CHANNEL_AUDIO_LANGUAGE = "audio-language";
public static final String CHANNEL_AUDIO_NAME = "audio-name";
public static final String CHANNEL_VIDEO_CODEC = "video-codec";
public static final String CHANNEL_VIDEO_INDEX = "video-index";
public static final String CHANNEL_VIDEO_HEIGHT = "video-height";
public static final String CHANNEL_VIDEO_WIDTH = "video-width";
public static final String CHANNEL_SUBTITLE_ENABLED = "subtitle-enabled";
public static final String CHANNEL_SUBTITLE_INDEX = "subtitle-index";
public static final String CHANNEL_SUBTITLE_LANGUAGE = "subtitle-language";
public static final String CHANNEL_SUBTITLE_NAME = "subtitle-name";
public static final String CHANNEL_CURRENTTIME = "currenttime";
public static final String CHANNEL_CURRENTTIMEPERCENTAGE = "currenttimepercentage";
public static final String CHANNEL_DURATION = "duration";
public static final String CHANNEL_UNIQUEID_IMDB = "uniqueid-imdb";
public static final String CHANNEL_UNIQUEID_IMDBTVSHOW = "uniqueid-imdbtvshow";
public static final String CHANNEL_UNIQUEID_TMDB = "uniqueid-tmdb";
public static final String CHANNEL_UNIQUEID_TMDBTVSHOW = "uniqueid-tmdbtvshow";
public static final String CHANNEL_UNIQUEID_TMDBEPISODE = "uniqueid-tmdbepisode";
public static final String CHANNEL_UNIQUEID_DOUBAN = "uniqueid-douban";
public static final String CHANNEL_MPAA = "mpaa";
public static final String CHANNEL_RATING = "rating";
public static final String CHANNEL_USERRATING = "userrating";
public static final String CHANNEL_SEASON = "season";
public static final String CHANNEL_EPISODE = "episode";
public static final String CHANNEL_TYPE_SHOWNOTIFICATION = "shownotification";
public static final String CHANNEL_TYPE_SHOWNOTIFICATION_PARAM_TITLE = "title";
public static final String CHANNEL_TYPE_SHOWNOTIFICATION_PARAM_DISPLAYTIME = "displayTime";
public static final String CHANNEL_TYPE_SHOWNOTIFICATION_PARAM_ICON = "icon";
// Module Properties
public static final String PROPERTY_VERSION = "version";
// Used for Discovery service
public static final String MANUFACTURER = "XBMC Foundation";
public static final String UPNP_DEVICE_TYPE = "MediaRenderer";
public static final String PVR_TV = "tv";
public static final String PVR_RADIO = "radio";
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of command options.
*
* @author Christoph Weitkamp - Initial contribution
*/
@Component(service = { DynamicCommandDescriptionProvider.class, KodiDynamicCommandDescriptionProvider.class })
@NonNullByDefault
public class KodiDynamicCommandDescriptionProvider extends BaseDynamicCommandDescriptionProvider {
@Activate
public KodiDynamicCommandDescriptionProvider(
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of state options while leaving other state description fields as original.
*
* @author Christoph Weitkamp - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, KodiDynamicStateDescriptionProvider.class })
@NonNullByDefault
public class KodiDynamicStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
@Activate
public KodiDynamicStateDescriptionProvider(
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal;
import java.util.EventListener;
import java.util.List;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.kodi.internal.model.KodiAudioStream;
import org.openhab.binding.kodi.internal.model.KodiSubtitle;
import org.openhab.binding.kodi.internal.model.KodiSystemProperties;
import org.openhab.binding.kodi.internal.protocol.KodiConnection;
import org.openhab.core.library.types.RawType;
/**
* Interface which has to be implemented by a class in order to get status
* updates from a {@link KodiConnection}
*
* @author Paul Frank - Initial contribution
* @author Christoph Weitkamp - Added channels for opening PVR TV or Radio streams
* @author Christoph Weitkamp - Improvements for playing audio notifications
*/
public interface KodiEventListener extends EventListener {
public enum KodiState {
PLAY,
PAUSE,
END,
STOP,
REWIND,
FASTFORWARD
}
public enum KodiPlaylistState {
ADD,
ADDED,
INSERT,
REMOVE,
REMOVED,
CLEAR
}
void updateConnectionState(boolean connected);
void updateScreenSaverState(boolean screenSaveActive);
void updatePlaylistState(KodiPlaylistState playlistState);
void updateVolume(int volume);
void updatePlayerState(KodiState state);
void updateMuted(boolean muted);
void updateMediaID(int mediaid);
void updateUniqueIDDouban(String uniqueid);
void updateUniqueIDImdb(String uniqueid);
void updateUniqueIDTmdb(String uniqueid);
void updateUniqueIDImdbtvshow(String uniqueid);
void updateUniqueIDTmdbtvshow(String uniqueid);
void updateUniqueIDTmdbepisode(String uniqueid);
void updateTitle(String title);
void updateOriginalTitle(String originaltitle);
void updateShowTitle(String title);
void updateAlbum(String album);
void updateArtistList(List<String> artistList);
void updateMediaType(String mediaType);
void updateGenreList(List<String> genreList);
void updatePVRChannel(String channel);
void updateThumbnail(@Nullable RawType thumbnail);
void updateFanart(@Nullable RawType fanart);
void updateAudioStreamOptions(List<KodiAudioStream> audioStreamList);
void updateAudioCodec(String codec);
void updateAudioName(String name);
void updateAudioIndex(int index);
void updateAudioChannels(int channels);
void updateAudioLanguage(String language);
void updateVideoCodec(String codec);
void updateVideoIndex(int index);
void updateVideoWidth(int width);
void updateVideoHeight(int height);
void updateSubtitleOptions(List<KodiSubtitle> subtitleList);
void updateSubtitleEnabled(boolean enabled);
void updateSubtitleIndex(int index);
void updateSubtitleName(String name);
void updateSubtitleLanguage(String language);
void updateCurrentTime(long currentTime);
void updateCurrentTimePercentage(double currentTimePercentage);
void updateDuration(long duration);
void updateSystemProperties(@Nullable KodiSystemProperties systemProperties);
void updateEpisode(int episode);
void updateSeason(int season);
void updateMediaFile(String mediafile);
void updateRating(double rating);
void updateUserRating(double rating);
void updateMpaa(String mpaa);
void updateCurrentProfile(String profile);
}

View File

@@ -0,0 +1,144 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal;
import static org.openhab.binding.kodi.internal.KodiBindingConstants.*;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.openhab.binding.kodi.internal.handler.KodiHandler;
import org.openhab.core.audio.AudioHTTPServer;
import org.openhab.core.audio.AudioSink;
import org.openhab.core.io.net.http.WebSocketFactory;
import org.openhab.core.net.HttpServiceUtil;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
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.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link KodiHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Paul Frank - Initial contribution
* @author Christoph Weitkamp - Improvements on channels for opening PVR TV or Radio streams
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.kodi")
public class KodiHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(KodiHandlerFactory.class);
private final AudioHTTPServer audioHTTPServer;
private final NetworkAddressService networkAddressService;
private final KodiDynamicCommandDescriptionProvider commandDescriptionProvider;
private final KodiDynamicStateDescriptionProvider stateDescriptionProvider;
private final WebSocketClient webSocketClient;
private final Map<String, @Nullable ServiceRegistration<AudioSink>> audioSinkRegistrations = new ConcurrentHashMap<>();
// url (scheme+server+port) to use for playing notification sounds
private @Nullable String callbackUrl;
@Activate
public KodiHandlerFactory(final @Reference AudioHTTPServer audioHTTPServer,
final @Reference NetworkAddressService networkAddressService,
final @Reference KodiDynamicCommandDescriptionProvider commandDescriptionProvider,
final @Reference KodiDynamicStateDescriptionProvider stateDescriptionProvider,
final @Reference WebSocketFactory webSocketFactory) {
this.audioHTTPServer = audioHTTPServer;
this.networkAddressService = networkAddressService;
this.commandDescriptionProvider = commandDescriptionProvider;
this.stateDescriptionProvider = stateDescriptionProvider;
this.webSocketClient = webSocketFactory.getCommonWebSocketClient();
}
@Override
protected void activate(ComponentContext componentContext) {
super.activate(componentContext);
Dictionary<String, Object> properties = componentContext.getProperties();
callbackUrl = (String) properties.get("callbackUrl");
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_KODI)) {
String callbackUrl = createCallbackUrl();
KodiHandler handler = new KodiHandler(thing, commandDescriptionProvider, stateDescriptionProvider,
webSocketClient, callbackUrl);
// register the Kodi as an audio sink
KodiAudioSink audioSink = new KodiAudioSink(handler, audioHTTPServer, callbackUrl);
@SuppressWarnings("unchecked")
ServiceRegistration<AudioSink> reg = (ServiceRegistration<AudioSink>) bundleContext
.registerService(AudioSink.class.getName(), audioSink, new Hashtable<>());
audioSinkRegistrations.put(thing.getUID().toString(), reg);
return handler;
}
return null;
}
private @Nullable String createCallbackUrl() {
if (callbackUrl != null) {
return callbackUrl;
} else {
final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
if (ipAddress == null) {
logger.warn("No network interface could be found.");
return null;
}
// we do not use SSL as it can cause certificate validation issues.
final int port = HttpServiceUtil.getHttpServicePort(bundleContext);
if (port == -1) {
logger.warn("Cannot find port of the http service.");
return null;
}
return "http://" + ipAddress + ":" + port;
}
}
@Override
public void unregisterHandler(Thing thing) {
super.unregisterHandler(thing);
ServiceRegistration<AudioSink> reg = audioSinkRegistrations.get(thing.getUID().toString());
if (reg != null) {
reg.unregister();
}
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal;
import org.openhab.binding.kodi.internal.KodiEventListener.KodiState;
/**
* The {@link KodiPlayerState} is responsible for saving the state of a player.
*
* @author Christoph Weitkamp - Initial contribution
*/
public class KodiPlayerState {
private int savedPlaylistID;
private int savedVolume;
private KodiState savedState;
public int getSavedPlaylistID() {
return savedPlaylistID;
}
public void setPlaylistID(int savedPlaylistID) {
this.savedPlaylistID = savedPlaylistID;
}
public int getSavedVolume() {
return savedVolume;
}
public void setSavedVolume(int savedVolume) {
this.savedVolume = savedVolume;
}
public KodiState getSavedState() {
return savedState;
}
public void setSavedState(KodiState savedState) {
this.savedState = savedState;
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.config;
/**
* Channel configuration from openHAB.
*
* @author Christoph Weitkamp - Initial contribution
*/
public class KodiChannelConfig {
private String group;
public String getGroup() {
return group;
}
public void setGroup(final String group) {
this.group = group;
}
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.config;
/**
* Thing configuration from openHAB.
*
* @author Christoph Weitkamp - Initial contribution
* @author Christoph Weitkamp - Improvements for playing audio notifications
*/
public class KodiConfig {
private String ipAddress;
private Integer port;
private Integer httpPort;
private String httpUser;
private String httpPassword;
private Integer refreshInterval;
private Integer notificationTimeout;
private Integer notificationVolume;
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Integer getHttpPort() {
return httpPort;
}
public void setHttpPort(Integer httpPort) {
this.httpPort = httpPort;
}
public String getHttpUser() {
return httpUser;
}
public void setHttpUser(String httpUser) {
this.httpUser = httpUser;
}
public String getHttpPassword() {
return httpPassword;
}
public void setHttpPassword(String httpPassword) {
this.httpPassword = httpPassword;
}
public Integer getRefreshInterval() {
return refreshInterval;
}
public void setRefreshInterval(Integer refreshInterval) {
this.refreshInterval = refreshInterval;
}
public Integer getNotificationTimeout() {
return notificationTimeout;
}
public void setNotificationTimeout(Integer notificationTimeout) {
this.notificationTimeout = notificationTimeout;
}
public Integer getNotificationVolume() {
return notificationVolume;
}
public void setNotificationVolume(Integer notificationVolume) {
this.notificationVolume = notificationVolume;
}
}

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.discovery;
import static org.openhab.binding.kodi.internal.KodiBindingConstants.*;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.jupnp.model.meta.RemoteDevice;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.upnp.UpnpDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Modified;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An UpnpDiscoveryParticipant which allows to discover Kodi AVRs.
*
* @author Paul Frank - Initial contribution
* @author Christoph Weitkamp - Use "discovery.kodi:background=false" to disable discovery service
*/
@Component(immediate = true, configurationPid = "discovery.kodi")
@NonNullByDefault
public class KodiUpnpDiscoveryParticipant implements UpnpDiscoveryParticipant {
private Logger logger = LoggerFactory.getLogger(KodiUpnpDiscoveryParticipant.class);
private boolean isAutoDiscoveryEnabled = true;
@Activate
protected void activate(ComponentContext componentContext) {
activateOrModifyService(componentContext);
}
@Modified
protected void modified(ComponentContext componentContext) {
activateOrModifyService(componentContext);
}
private void activateOrModifyService(ComponentContext componentContext) {
Dictionary<String, @Nullable Object> properties = componentContext.getProperties();
String autoDiscoveryPropertyValue = (String) properties.get("background");
if (autoDiscoveryPropertyValue != null && !autoDiscoveryPropertyValue.isEmpty()) {
isAutoDiscoveryEnabled = Boolean.valueOf(autoDiscoveryPropertyValue);
}
}
@Override
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
return SUPPORTED_THING_TYPES_UIDS;
}
@Override
public @Nullable DiscoveryResult createResult(RemoteDevice device) {
if (isAutoDiscoveryEnabled) {
ThingUID thingUid = getThingUID(device);
if (thingUid != null) {
String friendlyName = device.getDetails().getFriendlyName();
String label = friendlyName == null || friendlyName.isEmpty() ? device.getDisplayString()
: friendlyName;
Map<String, Object> properties = new HashMap<>();
properties.put(HOST_PARAMETER, device.getIdentity().getDescriptorURL().getHost());
DiscoveryResult result = DiscoveryResultBuilder.create(thingUid).withLabel(label)
.withProperties(properties).withRepresentationProperty(HOST_PARAMETER).build();
return result;
}
}
return null;
}
@Override
public @Nullable ThingUID getThingUID(RemoteDevice device) {
String manufacturer = device.getDetails().getManufacturerDetails().getManufacturer();
if (containsIgnoreCase(manufacturer, MANUFACTURER)) {
logger.debug("Manufacturer matched: search: {}, device value: {}.", MANUFACTURER, manufacturer);
String type = device.getType().getType();
if (containsIgnoreCase(type, UPNP_DEVICE_TYPE)) {
logger.debug("Device type matched: search: {}, device value: {}.", UPNP_DEVICE_TYPE, type);
return new ThingUID(THING_TYPE_KODI, device.getIdentity().getUdn().getIdentifierString());
}
}
return null;
}
private boolean containsIgnoreCase(final @Nullable String str, final String searchStr) {
return str != null && str.toLowerCase().contains(searchStr.toLowerCase());
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi audio stream (see https://kodi.wiki/view/JSON-RPC_API/v9#Player.Audio.Stream)
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiAudioStream {
private int bitrate;
private int channels;
private @NonNullByDefault({}) String codec;
private int index;
private @NonNullByDefault({}) String language;
private @NonNullByDefault({}) String name;
public int getBitrate() {
return bitrate;
}
public void setBitrate(int bitrate) {
this.bitrate = bitrate;
}
public int getChannels() {
return channels;
}
public void setChannels(int channels) {
this.channels = channels;
}
public String getCodec() {
return codec;
}
public void setCodec(String codec) {
this.codec = codec;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
/**
* Class representing a Kodi base item
*
* @author Christoph Weitkamp - Initial contribution
*/
public abstract class KodiBaseItem {
/**
* The label of the item
*/
private String label;
public String getLabel() {
return label;
}
public void setLabel(final String label) {
this.label = label;
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi duration (https://kodi.wiki/view/JSON-RPC_API/v9#Global.Time)
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiDuration {
/**
* The hours of the duration
*/
private long hours;
/**
* The minutes of the duration
*/
private long minutes;
/**
* The seconds of the duration
*/
private long seconds;
/**
* The milliseconds of the duration
*/
private long milliseconds;
public long getHours() {
return hours;
}
public void setHours(long hours) {
this.hours = hours;
}
public long getMinutes() {
return minutes;
}
public void setMinutes(long minutes) {
this.minutes = minutes;
}
public long getSeconds() {
return seconds;
}
public void setSeconds(long seconds) {
this.seconds = seconds;
}
public long getMilliseconds() {
return milliseconds;
}
public void setMilliseconds(long milliseconds) {
this.milliseconds = milliseconds;
}
/**
* Converts this KodiDuration to the total length in seconds.
*
* @return the total length of the duration in seconds
*/
public long toSeconds() {
return TimeUnit.MILLISECONDS.toSeconds(toMillis());
}
/**
* Converts this KodiDuration to the total length in milliseconds.
*
* @return the total length of the duration in milliseconds
*/
public long toMillis() {
return Duration.ofHours(hours).plusMinutes(minutes).plusSeconds(seconds).plusMillis(milliseconds).toMillis();
}
}

View File

@@ -0,0 +1,161 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Class representing a Kodi favorite.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiFavorite {
// handle titles which are wrapped e.g. [COLOR FFE95E01]Title[/COLOR]
private static final Pattern TITLE_PATTERN = Pattern.compile("(\\[COLOR\\s\\w{8}\\])|(\\[/COLOR\\])");
/**
* The title of the favorite
*/
private String title;
/**
* The type of the favorite
*/
private String favoriteType = "unknown";
/**
* The path of the favorite
*/
@Nullable
private String path;
/**
* The window of the favorite
*/
@Nullable
private String window;
/**
* The parameters of the favorites window
*/
@Nullable
private String windowParameter;
/**
* Constructs a favorite with the given title.
*
* @param title title of the favorite
*/
public KodiFavorite(final String title) {
this.title = title;
}
/**
* Returns the title of the favorite.
*
* @return the title of the favorite
*/
public String getTitle() {
return title;
}
/**
* Sets the title of the favorite.
*
* @param title title of the favorite
*/
public void setTitle(final String title) {
Matcher m = TITLE_PATTERN.matcher(title);
this.title = m.replaceAll("");
}
/**
* Returns the type of the favorite.
*
* @return the type of the favorite
*/
public String getFavoriteType() {
return favoriteType;
}
/**
* Sets the type of the favorite.
*
* @param favoriteType type of the favorite. Valid values are: "media", "window", "script" or "unknown"
*/
public void setFavoriteType(final String favoriteType) {
this.favoriteType = favoriteType;
}
/**
* Returns the path of the favorite.
*
* @return the path of the favorite
*/
@Nullable
public String getPath() {
return path;
}
/**
* Sets the path of the favorite.
*
* @param path path of the favorite
*/
public void setPath(final String path) {
this.path = path;
}
/**
* Returns the window of the favorite.
*
* @return the window of the favorite
*/
@Nullable
public String getWindow() {
return window;
}
/**
* Sets the window of the favorite.
*
* @param window the window of the favorite
*/
public void setWindow(final String window) {
this.window = window;
}
/**
* Returns the parameters of the favorites window.
*
* @return the parameters of the favorites window
*/
@Nullable
public String getWindowParameter() {
return windowParameter;
}
/**
* Sets the parameters of the favorites window.
*
* @param windowParameter the parameters of the favorites window
*/
public void setWindowParameter(final String windowParameter) {
this.windowParameter = windowParameter;
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
/**
* Class representing a Kodi PVR channel
*
* @author Christoph Weitkamp - Initial contribution
*/
public class KodiPVRChannel extends KodiBaseItem {
/**
* The PVR channel id
*/
private int channelId;
/**
* The PVR channel group id
*/
private int channelGroupId;
public int getId() {
return channelId;
}
public void setId(int channelId) {
this.channelId = channelId;
}
public int getChannelGroupId() {
return channelGroupId;
}
public void setChannelGroupId(int channelGroupId) {
this.channelGroupId = channelGroupId;
}
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
/**
* Class representing a Kodi PVR channel group
*
* @author Christoph Weitkamp - Initial contribution
*/
public class KodiPVRChannelGroup extends KodiBaseItem {
/**
* The PVR channel group id
*/
private int channelGroupId;
/**
* The PVR channel type
*/
private String channelType;
public int getId() {
return channelGroupId;
}
public void setId(final int channelGroupId) {
this.channelGroupId = channelGroupId;
}
public String getChannelType() {
return channelType;
}
public void setChannelType(final String channelType) {
this.channelType = channelType;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi profile (see https://kodi.wiki/view/JSON-RPC_API/v8#Profiles.GetProfiles)
*
* @author Jan Hendriks - Initial contribution
*/
@NonNullByDefault
public class KodiProfile extends KodiBaseItem {
private int lockmode;
private String thumbnail = "";
public int getLockmode() {
return lockmode;
}
public void setLockmode(int lockmode) {
this.lockmode = lockmode;
}
public String getThumbnail() {
return thumbnail;
}
public void setThumbnail(String thumbnail) {
this.thumbnail = thumbnail;
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi subtitle stream (see https://kodi.wiki/view/JSON-RPC_API/v9#Player.Subtitle)
*
* @author Meng Yiqi - Initial contribution
*/
@NonNullByDefault
public class KodiSubtitle {
private int index;
private String language = "";
private String name = "";
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
import com.google.gson.annotations.SerializedName;
/**
* Class representing Kodi system properties (https://kodi.wiki/view/JSON-RPC_API/v9#System.Property.Value)
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiSystemProperties {
@SerializedName("canhibernate")
private boolean canHibernate;
@SerializedName("canreboot")
private boolean canReboot;
@SerializedName("cansuspend")
private boolean canSuspend;
@SerializedName("canshutdown")
private boolean canShutdown;
public boolean canHibernate() {
return canHibernate;
}
public void setCanHibernate(boolean canHibernate) {
this.canHibernate = canHibernate;
}
public boolean canReboot() {
return canReboot;
}
public void setCanReboot(boolean canReboot) {
this.canReboot = canReboot;
}
public boolean canSuspend() {
return canSuspend;
}
public void setCansuspend(boolean canSuspend) {
this.canSuspend = canSuspend;
}
public boolean canShutdown() {
return canShutdown;
}
public void setCanShutdown(boolean canShutdown) {
this.canShutdown = canShutdown;
}
public boolean canQuit() {
return !canHibernate && !canReboot && !canShutdown && !canSuspend;
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
/**
* Class representing a Kodi UniqueID
*
* @author Meng Yiqi - Initial contribution
*/
public class KodiUniqueID {
private String imdb;
private String tmdb;
private String imdbtvshow;
private String tmdbtvshow;
private String tmdbepisode;
private String douban;
public String getImdb() {
return imdb;
}
public void setImdb(String imdb) {
this.imdb = imdb;
}
public String getTmdb() {
return tmdb;
}
public void setTmdb(String tmdb) {
this.tmdb = tmdb;
}
public String getImdbtvshow() {
return imdbtvshow;
}
public void setImdbtvshow(String imdbtvshow) {
this.imdbtvshow = imdbtvshow;
}
public String getTmdbtvshow() {
return tmdbtvshow;
}
public void setTmdbtvshow(String tmdbtvshow) {
this.tmdbtvshow = tmdbtvshow;
}
public String getTmdbepisode() {
return tmdbepisode;
}
public void setTmdbepisode(String tmdbepisode) {
this.tmdbtvshow = tmdbepisode;
}
public String getDouban() {
return douban;
}
public void setDouban(String douban) {
this.douban = douban;
}
}

View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.model;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Class representing a Kodi video stream (see https://kodi.wiki/view/JSON-RPC_API/v9#Player.Video.Stream)
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class KodiVideoStream {
private @NonNullByDefault({}) String codec;
private int height;
private int index;
private @NonNullByDefault({}) String language;
private @NonNullByDefault({}) String name;
private int width;
public String getCodec() {
return codec;
}
public void setCodec(String codec) {
this.codec = codec;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
}

View File

@@ -0,0 +1,227 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.protocol;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
/**
* KodiClientSocket implements the low level communication to Kodi through
* websocket. Usually this communication is done through port 9090
*
* @author Paul Frank - Initial contribution
*/
public class KodiClientSocket {
private final Logger logger = LoggerFactory.getLogger(KodiClientSocket.class);
private final ScheduledExecutorService scheduler;
private static final int REQUEST_TIMEOUT_MS = 60000;
private CountDownLatch commandLatch = null;
private JsonObject commandResponse = null;
private int nextMessageId = 1;
private boolean connected = false;
private final JsonParser parser = new JsonParser();
private final Gson mapper = new Gson();
private final URI uri;
private final WebSocketClient client;
private Session session;
private Future<?> sessionFuture;
private final KodiClientSocketEventListener eventHandler;
public KodiClientSocket(KodiClientSocketEventListener eventHandler, URI uri, ScheduledExecutorService scheduler,
WebSocketClient webSocketClient) {
this.eventHandler = eventHandler;
this.uri = uri;
this.scheduler = scheduler;
this.client = webSocketClient;
}
/**
* Attempts to create a connection to the Kodi host and begin listening for updates over the async http web socket
*
* @throws IOException
*/
public synchronized void open() throws IOException {
if (isConnected()) {
logger.warn("open: connection is already open");
}
KodiWebSocketListener socket = new KodiWebSocketListener();
ClientUpgradeRequest request = new ClientUpgradeRequest();
sessionFuture = client.connect(socket, uri, request);
}
/***
* Close this connection to the Kodi instance
*/
public void close() {
// if there is an old web socket then clean up and destroy
if (session != null) {
session.close();
session = null;
}
if (sessionFuture != null && !sessionFuture.isDone()) {
sessionFuture.cancel(true);
}
}
public boolean isConnected() {
if (session == null || !session.isOpen()) {
return false;
}
return connected;
}
@WebSocket
public class KodiWebSocketListener {
@OnWebSocketConnect
public void onConnect(Session wssession) {
logger.trace("Connected to server");
session = wssession;
connected = true;
if (eventHandler != null) {
scheduler.submit(() -> {
try {
eventHandler.onConnectionOpened();
} catch (Exception e) {
logger.debug("Error handling onConnectionOpened(): {}", e.getMessage(), e);
}
});
}
}
@OnWebSocketMessage
public void onMessage(String message) {
logger.trace("Message received from server: {}", message);
final JsonObject json = parser.parse(message).getAsJsonObject();
if (json.has("id")) {
int messageId = json.get("id").getAsInt();
if (messageId == nextMessageId - 1) {
commandResponse = json;
commandLatch.countDown();
}
} else {
logger.trace("Event received from server: {}", json);
if (eventHandler != null) {
scheduler.submit(() -> {
try {
eventHandler.handleEvent(json);
} catch (Exception e) {
logger.debug("Error handling event {} player state change message: {}", json,
e.getMessage(), e);
}
});
}
}
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
logger.trace("Closing a WebSocket due to {}", reason);
session = null;
connected = false;
if (eventHandler != null) {
scheduler.submit(() -> {
try {
eventHandler.onConnectionClosed();
} catch (Exception e) {
logger.debug("Error handling onConnectionClosed(): {}", e.getMessage(), e);
}
});
}
}
@OnWebSocketError
public void onError(Throwable error) {
logger.trace("Error occured: {}", error.getMessage());
onClose(0, error.getMessage());
}
}
private void sendMessage(String str) throws IOException {
if (isConnected()) {
logger.trace("send message: {}", str);
session.getRemote().sendString(str);
} else {
throw new IOException("Socket not initialized");
}
}
public JsonElement callMethod(String methodName) {
return callMethod(methodName, null);
}
public synchronized JsonElement callMethod(String methodName, JsonObject params) {
try {
JsonObject payloadObject = new JsonObject();
payloadObject.addProperty("jsonrpc", "2.0");
payloadObject.addProperty("id", nextMessageId);
payloadObject.addProperty("method", methodName);
if (params != null) {
payloadObject.add("params", params);
}
String message = mapper.toJson(payloadObject);
commandLatch = new CountDownLatch(1);
commandResponse = null;
nextMessageId++;
sendMessage(message);
if (commandLatch.await(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
logger.debug("callMethod returns: {}", commandResponse);
if (commandResponse.has("result")) {
return commandResponse.get("result");
} else {
JsonElement error = commandResponse.get("error");
logger.debug("Error received from server: {}", error);
return null;
}
} else {
logger.debug("Timeout during callMethod({}, {})", methodName, params);
return null;
}
} catch (IOException | InterruptedException e) {
logger.debug("Error during callMethod({}, {}): {}", methodName, params, e.getMessage(), e);
return null;
}
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.protocol;
import com.google.gson.JsonObject;
/**
* This interface has to be implemented for classes which need to be able to receive events from KodiClientSocket
*
* @author Paul Frank - Initial contribution
*/
public interface KodiClientSocketEventListener {
void handleEvent(JsonObject json);
void onConnectionClosed();
void onConnectionOpened();
}

View File

@@ -0,0 +1,307 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.utils;
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.config.core.ConfigConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is a simple file based cache implementation.
*
* @author Christoph Weitkamp - Initial contribution
*/
@NonNullByDefault
public class ByteArrayFileCache {
private final Logger logger = LoggerFactory.getLogger(ByteArrayFileCache.class);
private static final String MD5_ALGORITHM = "MD5";
static final String CACHE_FOLDER_NAME = "cache";
private static final char EXTENSION_SEPARATOR = '.';
private static final char UNIX_SEPARATOR = '/';
private static final char WINDOWS_SEPARATOR = '\\';
private final File cacheFolder;
static final long ONE_DAY_IN_MILLIS = TimeUnit.DAYS.toMillis(1);
private int expiry = 0;
private static final Map<String, File> FILES_IN_CACHE = new ConcurrentHashMap<>();
/**
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
* <code>$userdata/cache/$servicePID</code>.
*
* @param servicePID PID of the service
*/
public ByteArrayFileCache(String servicePID) {
// TODO track and limit folder size
// TODO support user specific folder
cacheFolder = new File(new File(ConfigConstants.getUserDataFolder(), CACHE_FOLDER_NAME), servicePID);
if (!cacheFolder.exists()) {
logger.debug("Creating cache folder '{}'", cacheFolder.getAbsolutePath());
cacheFolder.mkdirs();
}
logger.debug("Using cache folder '{}'", cacheFolder.getAbsolutePath());
}
/**
* Creates a new {@link ByteArrayFileCache} instance for a service. Creates a <code>cache</code> folder under
* <code>$userdata/cache/$servicePID/</code>.
*
* @param servicePID PID of the service
* @param int the days for how long the files stay in the cache valid. Must be positive. 0 to
* disables this functionality.
*/
public ByteArrayFileCache(String servicePID, int expiry) {
this(servicePID);
if (expiry < 0) {
throw new IllegalArgumentException("Cache expiration time must be greater than or equal to 0");
}
this.expiry = expiry;
}
/**
* Adds a file to the cache. If the cache previously contained a file for the key, the old file is replaced by the
* new content.
*
* @param key the key with which the file is to be associated
* @param content the content for the file to be associated with the specified key
*/
public void put(String key, byte[] content) {
writeFile(getUniqueFile(key), content);
}
/**
* Adds a file to the cache.
*
* @param key the key with which the file is to be associated
* @param content the content for the file to be associated with the specified key
*/
public void putIfAbsent(String key, byte[] content) {
File fileInCache = getUniqueFile(key);
if (fileInCache.exists()) {
logger.debug("File '{}' present in cache", fileInCache.getName());
// update time of last use
fileInCache.setLastModified(System.currentTimeMillis());
} else {
writeFile(fileInCache, content);
}
}
/**
* Adds a file to the cache and returns the content of the file.
*
* @param key the key with which the file is to be associated
* @param content the content for the file to be associated with the specified key
* @return the content of the file associated with the given key
*/
public byte[] putIfAbsentAndGet(String key, byte[] content) {
putIfAbsent(key, content);
return content;
}
/**
* Writes the given content to the given {@link File}.
*
* @param fileInCache the {@link File}
* @param content the content to be written
*/
private void writeFile(File fileInCache, byte[] content) {
logger.debug("Caching file '{}'", fileInCache.getName());
try {
Files.write(fileInCache.toPath(), content);
} catch (IOException e) {
logger.warn("Could not write file '{}' to cache", fileInCache.getName(), e);
}
}
/**
* Checks if the key is present in the cache.
*
* @param key the key whose presence in the cache is to be tested
* @return true if the cache contains a file for the specified key
*/
public boolean containsKey(String key) {
return getUniqueFile(key).exists();
}
/**
* Removes the file associated with the given key from the cache.
*
* @param key the key whose associated file is to be removed
*/
public void remove(String key) {
deleteFile(getUniqueFile(key));
}
/**
* Deletes the given {@link File}.
*
* @param fileInCache the {@link File}
*/
private void deleteFile(File fileInCache) {
if (fileInCache.exists()) {
logger.debug("Deleting file '{}' from cache", fileInCache.getName());
fileInCache.delete();
} else {
logger.debug("File '{}' not found in cache", fileInCache.getName());
}
}
/**
* Removes all files from the cache.
*/
public void clear() {
File[] filesInCache = cacheFolder.listFiles();
if (filesInCache != null && filesInCache.length > 0) {
logger.debug("Deleting all files from cache");
Arrays.stream(filesInCache).forEach(File::delete);
}
}
/**
* Removes expired files from the cache.
*/
public void clearExpired() {
// exit if expiry is set to 0 (disabled)
if (expiry <= 0) {
return;
}
File[] filesInCache = cacheFolder.listFiles();
if (filesInCache != null && filesInCache.length > 0) {
logger.debug("Deleting expired files from cache");
Arrays.stream(filesInCache).filter(file -> isExpired(file)).forEach(File::delete);
}
}
/**
* Checks if the given {@link File} is expired.
*
* @param fileInCache the {@link File}
* @return <code>true</code> if the file is expired, <code>false</code> otherwise
*/
private boolean isExpired(File fileInCache) {
// exit if expiry is set to 0 (disabled)
if (expiry <= 0) {
return false;
}
return expiry * ONE_DAY_IN_MILLIS < System.currentTimeMillis() - fileInCache.lastModified();
}
/**
* Returns the content of the file associated with the given key, if it is present.
*
* @param key the key whose associated file is to be returned
* @return the content of the file associated with the given key
*/
public byte[] get(String key) {
return readFile(getUniqueFile(key));
}
/**
* Reads the content from the given {@link File}, if it is present.
*
* @param fileInCache the {@link File}
* @return the content of the file
*/
private byte[] readFile(File fileInCache) {
if (fileInCache.exists()) {
logger.debug("Reading file '{}' from cache", fileInCache.getName());
// update time of last use
fileInCache.setLastModified(System.currentTimeMillis());
try {
return Files.readAllBytes(fileInCache.toPath());
} catch (IOException e) {
logger.warn("Could not read file '{}' from cache", fileInCache.getName(), e);
}
} else {
logger.debug("File '{}' not found in cache", fileInCache.getName());
}
return new byte[0];
}
/**
* Creates a unique {@link File} from the key with which the file is to be associated.
*
* @param key the key with which the file is to be associated
* @return unique file for the file associated with the given key
*/
File getUniqueFile(String key) {
String uniqueFileName = getUniqueFileName(key);
if (FILES_IN_CACHE.containsKey(uniqueFileName)) {
return FILES_IN_CACHE.get(uniqueFileName);
} else {
String fileExtension = getFileExtension(key);
File fileInCache = new File(cacheFolder,
uniqueFileName + (fileExtension == null ? "" : EXTENSION_SEPARATOR + fileExtension));
FILES_IN_CACHE.put(uniqueFileName, fileInCache);
return fileInCache;
}
}
/**
* Gets the extension of a file name.
*
* @param fileName the file name to retrieve the extension of
* @return the extension of the file or null if none exists
*/
@Nullable
String getFileExtension(String fileName) {
int extensionPos = fileName.lastIndexOf(EXTENSION_SEPARATOR);
int lastSeparatorPos = Math.max(fileName.lastIndexOf(UNIX_SEPARATOR), fileName.lastIndexOf(WINDOWS_SEPARATOR));
return lastSeparatorPos > extensionPos ? null : fileName.substring(extensionPos + 1).replaceFirst("\\?.*$", "");
}
/**
* Creates a unique file name from the key with which the file is to be associated.
*
* @param key the key with which the file is to be associated
* @return unique file name for the file associated with the given key
*/
String getUniqueFileName(String key) {
try {
MessageDigest md = MessageDigest.getInstance(MD5_ALGORITHM);
byte[] bytesOfKey = key.getBytes(StandardCharsets.UTF_8);
byte[] md5Hash = md.digest(bytesOfKey);
BigInteger bigInt = new BigInteger(1, md5Hash);
String fileNameHash = bigInt.toString(16);
// We need to zero pad it if you actually want the full 32 chars
while (fileNameHash.length() < 32) {
fileNameHash = "0" + fileNameHash;
}
return fileNameHash;
} catch (NoSuchAlgorithmException ex) {
// should not happen
logger.error("Could not create MD5 hash for key '{}'", key, ex);
return key;
}
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="kodi" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Kodi Binding</name>
<description>This is the binding for Kodi.</description>
<author>Paul Frank</author>
<config-description>
<parameter name="callbackUrl" type="text">
<label>Callback URL</label>
<description>url to use for playing notification sounds, e.g. http://192.168.0.2:8080</description>
<required>false</required>
</parameter>
</config-description>
</binding:binding>

View File

@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="https://openhab.org/schemas/config-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/config-description/v1.0.0 https://openhab.org/schemas/config-description-1.0.0.xsd">
<config-description uri="thing-type:kodi:kodi">
<parameter-group name="network">
<label>Network</label>
<description>Network settings.</description>
</parameter-group>
<parameter-group name="connection">
<label>Connection</label>
<description>Connection settings.</description>
</parameter-group>
<parameter-group name="notification">
<label>Notification</label>
<description>Notification settings.</description>
</parameter-group>
<parameter name="ipAddress" type="text" required="true" groupName="network">
<label>Network Address</label>
<description>The IP or host name of the Kodi</description>
<context>network-address</context>
</parameter>
<parameter name="port" type="integer" required="true" min="1" max="65535" groupName="network">
<label>Web Socket Service Port</label>
<description>Port for the web socket service</description>
<default>9090</default>
</parameter>
<parameter name="httpPort" type="integer" required="true" min="1" max="65535" groupName="network">
<label>HTTP Service Port</label>
<description>Port for the HTTP service.</description>
<default>8080</default>
<advanced>true</advanced>
</parameter>
<parameter name="httpUser" type="text" required="false" groupName="network">
<label>Username</label>
<description>User name to access HTTP service.</description>
<advanced>true</advanced>
</parameter>
<parameter name="httpPassword" type="text" required="false" groupName="network">
<context>password</context>
<label>Password</label>
<description>Password to access the HTTP service.</description>
<advanced>true</advanced>
</parameter>
<parameter name="refreshInterval" type="integer" required="false" min="1" max="60" unit="s"
groupName="connection">
<label>Refresh Interval</label>
<description>The refresh interval to poll Kodi API (in s).</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
<parameter name="notificationTimeout" type="integer" unit="s" required="true" groupName="notification">
<label>Notification Timeout</label>
<description>Maximum amount of time for which the notification will be played (in s).</description>
<default>20</default>
</parameter>
<parameter name="notificationVolume" type="integer" min="0" max="100" step="1" unit="%"
groupName="notification">
<label>Notification Sound Volume</label>
<description>Specifies the volume applied to a notification sound (in %).</description>
</parameter>
</config-description>
<config-description uri="channel-type:kodi:pvr-channel">
<parameter name="group" type="text" required="true">
<label>PVR Channel Group</label>
<description>The PVR channel group name used to identify the channel id.</description>
<default>All channels</default>
</parameter>
</config-description>
<config-description uri="channel-type:kodi:shownotification">
<parameter name="title" type="text" required="false">
<label>Notification Title</label>
<description>Title to show for the notification.</description>
<default>openHAB</default>
</parameter>
<parameter name="displayTime" type="integer" required="false" min="100" unit="ms">
<label>Display Time</label>
<description>Time the notification is displayed.</description>
<default>5000</default>
</parameter>
<parameter name="icon" type="text" required="false">
<label>Icon</label>
<description>Icon name to show with the notification.</description>
</parameter>
</config-description>
</config-description:config-descriptions>

View File

@@ -0,0 +1,114 @@
# binding
binding.kodi.name = Kodi Binding
binding.kodi.description = Dieses Binding integriert das Kodi Media Center. Durch dieses kann das Media Center gesteuert werden.
# binding config
binding.config.kodi.callbackUrl.label = Callback URL
binding.config.kodi.callbackUrl.description = URL zum Abspielen von Benachrichtigungen (z.B. http://192.168.0.2:8080).
# thing types
thing-type.kodi.kodi.label = Kodi Media Center
thing-type.kodi.kodi.description = Kodi Media Center
# thing types config
thing-type.config.kodi.kodi.group.network.label = Netzwerk
thing-type.config.kodi.kodi.group.network.description = Einstellungen für das Netzwerk.
thing-type.config.kodi.kodi.group.connection.label = Verbindung
thing-type.config.kodi.kodi.group.connection.description = Einstellungen für die Verbindung.
thing-type.config.kodi.kodi.group.notification.label = Benachrichtigungen
thing-type.config.kodi.kodi.group.notification.description = Einstellungen für Benachrichtigungen.
thing-type.config.kodi.kodi.ipAddress.label = IP-Adresse
thing-type.config.kodi.kodi.ipAddress.description = Lokale IP-Adresse oder Hostname des Kodi Media Centers.
thing-type.config.kodi.kodi.port.label = Port (Webservice)
thing-type.config.kodi.kodi.port.description = Port des Kodi Media Centers Webservices.
thing-type.config.kodi.kodi.httpPort.label = Port (Weboberfläche)
thing-type.config.kodi.kodi.httpPort.description = Port der Kodi Media Center Weboberfläche.
thing-type.config.kodi.kodi.httpUser.label = Benutzer
thing-type.config.kodi.kodi.httpUser.description = Benutzer zur Authentifizierung an der Kodi Media Center Weboberfläche.
thing-type.config.kodi.kodi.httpPassword.label = Passwort
thing-type.config.kodi.kodi.httpPassword.description = Passwort zur Authentifizierung an der Kodi Media Center Weboberfläche.
thing-type.config.kodi.kodi.refreshInterval.label = Abfrageintervall
thing-type.config.kodi.kodi.refreshInterval.description = Intervall zur Abfrage des Kodi Media Centers (in s).
thing-type.config.kodi.kodi.notificationTimeout.label = Timeout für Benachrichtigungen
thing-type.config.kodi.kodi.notificationTimeout.description = Timeout für Benachrichtigungen (in s).
thing-type.config.kodi.kodi.notificationVolume.label = Lautstärke von Benachrichtigungen
thing-type.config.kodi.kodi.notificationVolume.description = Lautstärke von Benachrichtigungen (in %).
# channel types
channel-type.kodi.stop.label = Stop
channel-type.kodi.stop.description = Ermöglicht das Stoppen der Wiedergabe.
channel-type.kodi.playuri.label = URI abspielen
channel-type.kodi.playuri.description = Ermöglicht das Abspielen einer URI.
channel-type.kodi.playfavorite.label = Favorit abspielen oder öffnen
channel-type.kodi.playfavorite.description = Ermöglicht das Abspielen oder Öffnen eines Favoriten.
channel-type.kodi.pvr-open-tv.label = PVR TV Kanal
channel-type.kodi.pvr-open-tv.description = Ermöglicht das Abspielen eines PVR TV Kanals.
channel-type.kodi.pvr-open-radio.label = PVR Radio Kanal
channel-type.kodi.pvr-open-radio.description = Ermöglicht das Abspielen eines PVR Radio Kanals.
channel-type.kodi.pvr-channel.label = PVR Kanal
channel-type.kodi.pvr-channel.description = Zeigt den Titel des aktuellen PVR Kanals an.
channel-type.kodi.shownotification.label = Nachricht anzeigen
channel-type.kodi.shownotification.description = Ermöglicht das Anzeigen einer Nachricht auf dem Kodi Media Center.
channel-type.kodi.input.label = Tastendruck
channel-type.kodi.input.description = Ermöglicht das Senden eines Eingabebefehls zur Steuerung der UI an das Kodi Media Center.
channel-type.kodi.input.state.option.Back = Zurück
channel-type.kodi.input.state.option.ContextMenu = Kontextmenü
channel-type.kodi.input.state.option.Down = Runter
channel-type.kodi.input.state.option.Home = Home
channel-type.kodi.input.state.option.Info = Info Dialog
channel-type.kodi.input.state.option.Left = Links
channel-type.kodi.input.state.option.Right = Rechts
channel-type.kodi.input.state.option.Select = Auswählen
channel-type.kodi.input.state.option.ShowCodec = Codec Info
channel-type.kodi.input.state.option.ShowOSD = Bildschirmanzeige
channel-type.kodi.input.state.option.ShowPlayerProcessInfo = Player Info
channel-type.kodi.input.state.option.Up = Hoch
channel-type.kodi.inputtext.label = Texteingabe
channel-type.kodi.inputtext.description = Ermöglicht das Senden eines Eingabetextes (Unicode) an das Kodi Media Center.
channel-type.kodi.inputaction.label = Aktion
channel-type.kodi.inputaction.description = Ermöglicht das Senden einer vordefinierten Aktion an das Kodi Media Center.
channel-type.kodi.systemcommand.label = Systembefehl
channel-type.kodi.systemcommand.description = Ermöglicht das Senden eines Systembefehls um das Kodi Media Center neu zu starten, in den Ruhezustand oder Stromsparmodus zu versetzen oder herunterzufahren.
channel-type.kodi.systemcommand.command.option.Shutdown = Herunterfahren
channel-type.kodi.systemcommand.command.option.Suspend = Bereitschaft
channel-type.kodi.systemcommand.command.option.Hibernate = Ruhezustand
channel-type.kodi.systemcommand.command.option.Reboot = Neustart
channel-type.kodi.systemcommand.command.option.Quit = Verlassen
channel-type.kodi.showtitle.label = Show Titel
channel-type.kodi.showtitle.description = Zeigt den Titel der aktuellen Show an.
channel-type.kodi.album.label = Album
channel-type.kodi.album.description = Zeigt das Album des aktuellen Stücks an.
channel-type.kodi.mediatype.label = Medientyp
channel-type.kodi.mediatype.description = Zeigt den Medientyp des aktuellen Stücks oder Films (z. B. movie, song) an.
channel-type.kodi.genreList.label = Genre Liste
channel-type.kodi.genreList.description = Zeigt eine durch Komma getrennte Liste der Genres des aktuellen Stücks oder Films an.
channel-type.kodi.thumbnail.label = Thumbnail
channel-type.kodi.thumbnail.description = Zeigt das Thumbnail des aktuellen Stücks oder Films an.
channel-type.kodi.fanart.label = Fan Art
channel-type.kodi.fanart.description = Zeigt das Fan Art des aktuellen Stücks oder Films an.
channel-type.kodi.playnotification.label = Benachrichtigung abspielen
channel-type.kodi.playnotification.description = Ermöglicht das Abspielen einer Benachrichtigung.
channel-type.kodi.codec.label = Codec
channel-type.kodi.codec.description = Zeigt den Codec des aktuellen Stücks oder Films an.
thing-type.kodi.kodi.channel.audio-codec.label = Audio-Codec
thing-type.kodi.kodi.channel.audio-codec.description = Zeigt den Audio-Codec des aktuellen Stücks oder Films an.
thing-type.kodi.kodi.channel.video-codec.label = Video-Codec
thing-type.kodi.kodi.channel.video-codec.description = Zeigt den Video-Codec des aktuellen Stücks oder Films an.
channel-type.kodi.currenttime.label = Laufzeit
channel-type.kodi.currenttime.description = Zeigt die Laufzeit des aktuellen Stücks oder Films an.
channel-type.kodi.currenttimepercentage.label = Laufzeit in Prozent
channel-type.kodi.currenttimepercentage.description = Zeigt die Laufzeit des aktuellen Stücks oder Films an.
channel-type.kodi.duration.label = Dauer
channel-type.kodi.duration.description = Zeigt die Dauer des aktuellen Stücks oder Films an.
# channel types config
channel-type.config.kodi.pvr-channel.group.label = PVR Kanal Gruppe
channel-type.config.kodi.pvr-channel.group.description = Gruppe der verwendbaren PVR Kanäle.
channel-type.config.kodi.shownotification.title.label = Titel
channel-type.config.kodi.shownotification.title.description = Titel der Nachricht.
channel-type.config.kodi.shownotification.displayTime.label = Anzeigezeit
channel-type.config.kodi.shownotification.displayTime.description = Anzeigezeit der Nachricht.
channel-type.config.kodi.shownotification.icon.label = Icon
channel-type.config.kodi.shownotification.icon.description = Icon der Nachricht.

View File

@@ -0,0 +1,557 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="kodi"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- Kodi Thing Type -->
<thing-type id="kodi" extensible="shownotification">
<label>Kodi Mediacenter</label>
<description>Kodi Mediacenter Binding</description>
<channels>
<channel id="volume" typeId="system.volume"/>
<channel id="mute" typeId="system.mute"/>
<channel id="control" typeId="system.media-control"/>
<channel id="stop" typeId="stop"/>
<channel id="playuri" typeId="playuri"/>
<channel id="pvr-open-tv" typeId="pvr-open-tv"/>
<channel id="pvr-open-radio" typeId="pvr-open-radio"/>
<channel id="pvr-channel" typeId="pvr-channel"/>
<channel id="shownotification" typeId="shownotification"/>
<channel id="input" typeId="input"/>
<channel id="inputtext" typeId="inputtext"/>
<channel id="inputaction" typeId="inputaction"/>
<channel id="systemcommand" typeId="systemcommand"/>
<channel id="title" typeId="system.media-title"/>
<channel id="originaltitle" typeId="system.media-title"/>
<channel id="showtitle" typeId="showtitle"/>
<channel id="album" typeId="album"/>
<channel id="artist" typeId="system.media-artist"/>
<channel id="mediatype" typeId="mediatype"/>
<channel id="genreList" typeId="genreList"/>
<channel id="thumbnail" typeId="thumbnail"/>
<channel id="fanart" typeId="fanart"/>
<channel id="playfavorite" typeId="playfavorite"/>
<channel id="playnotification" typeId="playnotification"/>
<channel id="profile" typeId="profile"/>
<channel id="audio-name" typeId="name">
<label>Audio Name</label>
</channel>
<channel id="audio-channels" typeId="channels">
<label>Audio Channels</label>
</channel>
<channel id="audio-index" typeId="index">
<label>Audio Index</label>
</channel>
<channel id="audio-language" typeId="language">
<label>Audio Language</label>
</channel>
<channel id="audio-codec" typeId="codec">
<label>Audio Codec</label>
<description>Audio codec of currently playing media</description>
</channel>
<channel id="video-width" typeId="width"/>
<channel id="video-height" typeId="height"/>
<channel id="video-index" typeId="index">
<label>Video Index</label>
</channel>
<channel id="video-codec" typeId="codec">
<label>Video Codec</label>
<description>Video codec of currently playing media</description>
</channel>
<channel id="subtitle-enabled" typeId="enabled">
<label>Subtitle Enabled</label>
</channel>
<channel id="subtitle-name" typeId="name">
<label>Subtitle Name</label>
</channel>
<channel id="subtitle-index" typeId="index">
<label>Subtitle Index</label>
</channel>
<channel id="subtitle-language" typeId="language">
<label>Subtitle Language</label>
</channel>
<channel id="currenttime" typeId="currenttime"/>
<channel id="currenttimepercentage" typeId="currenttimepercentage"/>
<channel id="duration" typeId="duration"/>
<channel id="mediafile" typeId="file"/>
<channel id="mediaid" typeId="id">
<label>Kodi Media ID</label>
<description>media_id in kodi database</description>
</channel>
<channel id="uniqueid-douban" typeId="uniqueid">
<label>Douban ID</label>
<description>example usage - http://www.douban.com/subject/3036644/</description>
</channel>
<channel id="uniqueid-imdb" typeId="uniqueid">
<label>IMDB ID</label>
<description>example usage - http://www.imdb.com/title/tt7207268/</description>
</channel>
<channel id="uniqueid-imdbtvshow" typeId="uniqueid">
<label>IMDB TVSHOW ID</label>
<description>example usage - http://www.imdb.com/title/tt0426769/</description>
</channel>
<channel id="uniqueid-tmdb" typeId="uniqueid">
<label>TMDB ID</label>
<description>example usage - http://www.themoviedb.org/movie/123456789</description>
</channel>
<channel id="uniqueid-tmdbepisode" typeId="uniqueid">
<label>TMDB EPISODE ID</label>
<description>example usage - http://www.themoviedb.org/tv/12225/season/5/episode/15/</description>
</channel>
<channel id="uniqueid-tmdbtvshow" typeId="uniqueid">
<label>TMDB TVSHOW ID</label>
<description>example usage - http://www.themoviedb.org/tv/12225/</description>
</channel>
<channel id="mpaa" typeId="mpaa"/>
<channel id="rating" typeId="rating"/>
<channel id="userrating" typeId="rating">
<label>User Rating</label>
</channel>
</channels>
<properties>
<property name="version">unknown</property>
</properties>
<representation-property>ipAddress</representation-property>
<config-description-ref uri="thing-type:kodi:kodi"/>
</thing-type>
<!-- Kodi Commands -->
<channel-type id="stop">
<item-type>Switch</item-type>
<label>Stop</label>
<description>Stops the player. ON if the player is stopped.</description>
</channel-type>
<channel-type id="playuri" advanced="true">
<item-type>String</item-type>
<label>Play URI</label>
<description>Play the given URI</description>
</channel-type>
<channel-type id="playfavorite">
<item-type>String</item-type>
<label>Play or Open a Favorite</label>
<description>Play or open the given favorite by sending a command with the favorite's title</description>
</channel-type>
<channel-type id="pvr-open-tv" advanced="true">
<item-type>String</item-type>
<label>Play PVR TV</label>
<description>Play the given PVR TV channel by sending a command with the channel's name</description>
<state pattern="%s"/>
<config-description-ref uri="channel-type:kodi:pvr-channel"/>
</channel-type>
<channel-type id="pvr-open-radio" advanced="true">
<item-type>String</item-type>
<label>Play PVR Radio</label>
<description>Play the given PVR Radio channel by sending a command with the channel's name</description>
<state pattern="%s"/>
<config-description-ref uri="channel-type:kodi:pvr-channel"/>
</channel-type>
<channel-type id="shownotification" advanced="true">
<item-type>String</item-type>
<label>Show Notification</label>
<description>Shows a notification on the UI</description>
<config-description-ref uri="channel-type:kodi:shownotification"/>
</channel-type>
<channel-type id="playnotification" advanced="true">
<item-type>String</item-type>
<label>Play Notification</label>
<description>Plays a notification sound by a given URI</description>
</channel-type>
<channel-type id="input" advanced="true">
<item-type>String</item-type>
<label>Send a Key</label>
<description>Sends a key stroke to Kodi to navigate in the UI</description>
<state>
<options>
<option value="Back">Back</option>
<option value="ContextMenu">Show context Menu</option>
<option value="Down">Down</option>
<option value="Home">Home</option>
<option value="Info">Show information dialog</option>
<option value="Left">Left</option>
<option value="Right">Right</option>
<option value="Select">Select</option>
<option value="ShowCodec">Show codec information</option>
<option value="ShowOSD">Show on-screen display</option>
<option value="ShowPlayerProcessInfo">Show player process info</option>
<option value="Up">Up</option>
</options>
</state>
</channel-type>
<channel-type id="inputtext" advanced="true">
<item-type>String</item-type>
<label>Sends Text as Input</label>
<description>Sends a generic input (unicode) text to Kodi</description>
</channel-type>
<channel-type id="inputaction" advanced="true">
<item-type>String</item-type>
<label>Execute an Action</label>
<description>Sends a predefined action to Kodi to control the UI and/or perform other tasks</description>
<state>
<options>
<option value="left">left</option>
<option value="right">right</option>
<option value="up">up</option>
<option value="down">down</option>
<option value="pageup">pageup</option>
<option value="pagedown">pagedown</option>
<option value="select">select</option>
<option value="highlight">highlight</option>
<option value="parentdir">parentdir</option>
<option value="parentfolder">parentfolder</option>
<option value="back">back</option>
<option value="menu">menu</option>
<option value="previousmenu">previousmenu</option>
<option value="info">info</option>
<option value="pause">pause</option>
<option value="stop">stop</option>
<option value="skipnext">skipnext</option>
<option value="skipprevious">skipprevious</option>
<option value="fullscreen">fullscreen</option>
<option value="aspectratio">aspectratio</option>
<option value="stepforward">stepforward</option>
<option value="stepback">stepback</option>
<option value="bigstepforward">bigstepforward</option>
<option value="bigstepback">bigstepback</option>
<option value="chapterorbigstepforward">chapterorbigstepforward</option>
<option value="chapterorbigstepback">chapterorbigstepback</option>
<option value="osd">osd</option>
<option value="showsubtitles">showsubtitles</option>
<option value="nextsubtitle">nextsubtitle</option>
<option value="cyclesubtitle">cyclesubtitle</option>
<option value="playerdebug">playerdebug</option>
<option value="codecinfo">codecinfo</option>
<option value="playerprocessinfo">playerprocessinfo</option>
<option value="nextpicture">nextpicture</option>
<option value="previouspicture">previouspicture</option>
<option value="zoomout">zoomout</option>
<option value="zoomin">zoomin</option>
<option value="playlist">playlist</option>
<option value="queue">queue</option>
<option value="zoomnormal">zoomnormal</option>
<option value="zoomlevel1">zoomlevel1</option>
<option value="zoomlevel2">zoomlevel2</option>
<option value="zoomlevel3">zoomlevel3</option>
<option value="zoomlevel4">zoomlevel4</option>
<option value="zoomlevel5">zoomlevel5</option>
<option value="zoomlevel6">zoomlevel6</option>
<option value="zoomlevel7">zoomlevel7</option>
<option value="zoomlevel8">zoomlevel8</option>
<option value="zoomlevel9">zoomlevel9</option>
<option value="nextcalibration">nextcalibration</option>
<option value="resetcalibration">resetcalibration</option>
<option value="analogmove">analogmove</option>
<option value="analogmovex">analogmovex</option>
<option value="analogmovey">analogmovey</option>
<option value="rotate">rotate</option>
<option value="rotateccw">rotateccw</option>
<option value="close">close</option>
<option value="subtitledelayminus">subtitledelayminus</option>
<option value="subtitledelay">subtitledelay</option>
<option value="subtitledelayplus">subtitledelayplus</option>
<option value="audiodelayminus">audiodelayminus</option>
<option value="audiodelay">audiodelay</option>
<option value="audiodelayplus">audiodelayplus</option>
<option value="subtitleshiftup">subtitleshiftup</option>
<option value="subtitleshiftdown">subtitleshiftdown</option>
<option value="subtitlealign">subtitlealign</option>
<option value="audionextlanguage">audionextlanguage</option>
<option value="verticalshiftup">verticalshiftup</option>
<option value="verticalshiftdown">verticalshiftdown</option>
<option value="nextresolution">nextresolution</option>
<option value="audiotoggledigital">audiotoggledigital</option>
<option value="number0">number0</option>
<option value="number1">number1</option>
<option value="number2">number2</option>
<option value="number3">number3</option>
<option value="number4">number4</option>
<option value="number5">number5</option>
<option value="number6">number6</option>
<option value="number7">number7</option>
<option value="number8">number8</option>
<option value="number9">number9</option>
<option value="smallstepback">smallstepback</option>
<option value="fastforward">fastforward</option>
<option value="rewind">rewind</option>
<option value="play">play</option>
<option value="playpause">playpause</option>
<option value="switchplayer">switchplayer</option>
<option value="delete">delete</option>
<option value="copy">copy</option>
<option value="move">move</option>
<option value="screenshot">screenshot</option>
<option value="rename">rename</option>
<option value="togglewatched">togglewatched</option>
<option value="scanitem">scanitem</option>
<option value="reloadkeymaps">reloadkeymaps</option>
<option value="volumeup">volumeup</option>
<option value="volumedown">volumedown</option>
<option value="mute">mute</option>
<option value="backspace">backspace</option>
<option value="scrollup">scrollup</option>
<option value="scrolldown">scrolldown</option>
<option value="analogfastforward">analogfastforward</option>
<option value="analogrewind">analogrewind</option>
<option value="moveitemup">moveitemup</option>
<option value="moveitemdown">moveitemdown</option>
<option value="contextmenu">contextmenu</option>
<option value="shift">shift</option>
<option value="symbols">symbols</option>
<option value="cursorleft">cursorleft</option>
<option value="cursorright">cursorright</option>
<option value="showtime">showtime</option>
<option value="analogseekforward">analogseekforward</option>
<option value="analogseekback">analogseekback</option>
<option value="showpreset">showpreset</option>
<option value="nextpreset">nextpreset</option>
<option value="previouspreset">previouspreset</option>
<option value="lockpreset">lockpreset</option>
<option value="randompreset">randompreset</option>
<option value="increasevisrating">increasevisrating</option>
<option value="decreasevisrating">decreasevisrating</option>
<option value="showvideomenu">showvideomenu</option>
<option value="enter">enter</option>
<option value="increaserating">increaserating</option>
<option value="decreaserating">decreaserating</option>
<option value="setrating">setrating</option>
<option value="togglefullscreen">togglefullscreen</option>
<option value="nextscene">nextscene</option>
<option value="previousscene">previousscene</option>
<option value="nextletter">nextletter</option>
<option value="prevletter">prevletter</option>
<option value="jumpsms2">jumpsms2</option>
<option value="jumpsms3">jumpsms3</option>
<option value="jumpsms4">jumpsms4</option>
<option value="jumpsms5">jumpsms5</option>
<option value="jumpsms6">jumpsms6</option>
<option value="jumpsms7">jumpsms7</option>
<option value="jumpsms8">jumpsms8</option>
<option value="jumpsms9">jumpsms9</option>
<option value="filter">filter</option>
<option value="filterclear">filterclear</option>
<option value="filtersms2">filtersms2</option>
<option value="filtersms3">filtersms3</option>
<option value="filtersms4">filtersms4</option>
<option value="filtersms5">filtersms5</option>
<option value="filtersms6">filtersms6</option>
<option value="filtersms7">filtersms7</option>
<option value="filtersms8">filtersms8</option>
<option value="filtersms9">filtersms9</option>
<option value="firstpage">firstpage</option>
<option value="lastpage">lastpage</option>
<option value="guiprofile">guiprofile</option>
<option value="red">red</option>
<option value="green">green</option>
<option value="yellow">yellow</option>
<option value="blue">blue</option>
<option value="increasepar">increasepar</option>
<option value="decreasepar">decreasepar</option>
<option value="volampup">volampup</option>
<option value="volampdown">volampdown</option>
<option value="volumeamplification">volumeamplification</option>
<option value="createbookmark">createbookmark</option>
<option value="createepisodebookmark">createepisodebookmark</option>
<option value="settingsreset">settingsreset</option>
<option value="settingslevelchange">settingslevelchange</option>
<option value="stereomode">stereomode</option>
<option value="nextstereomode">nextstereomode</option>
<option value="previousstereomode">previousstereomode</option>
<option value="togglestereomode">togglestereomode</option>
<option value="stereomodetomono">stereomodetomono</option>
<option value="channelup">channelup</option>
<option value="channeldown">channeldown</option>
<option value="previouschannelgroup">previouschannelgroup</option>
<option value="nextchannelgroup">nextchannelgroup</option>
<option value="playpvr">playpvr</option>
<option value="playpvrtv">playpvrtv</option>
<option value="playpvrradio">playpvrradio</option>
<option value="record">record</option>
<option value="togglecommskip">togglecommskip</option>
<option value="showtimerrule">showtimerrule</option>
<option value="leftclick">leftclick</option>
<option value="rightclick">rightclick</option>
<option value="middleclick">middleclick</option>
<option value="doubleclick">doubleclick</option>
<option value="longclick">longclick</option>
<option value="wheelup">wheelup</option>
<option value="wheeldown">wheeldown</option>
<option value="mousedrag">mousedrag</option>
<option value="mousemove">mousemove</option>
<option value="tap">tap</option>
<option value="longpress">longpress</option>
<option value="pangesture">pangesture</option>
<option value="zoomgesture">zoomgesture</option>
<option value="rotategesture">rotategesture</option>
<option value="swipeleft">swipeleft</option>
<option value="swiperight">swiperight</option>
<option value="swipeup">swipeup</option>
<option value="swipedown">swipedown</option>
<option value="error">error</option>
<option value="noop">noop</option>
</options>
</state>
</channel-type>
<channel-type id="systemcommand" advanced="true">
<item-type>String</item-type>
<label>Send System Command</label>
<description>Sends a system command to Kodi. This allows to shutdown/suspend/hibernate/reboot/quit Kodi</description>
<command>
<options>
<option value="Shutdown">Shutdown</option>
<option value="Suspend">Suspend</option>
<option value="Hibernate">Hibernate</option>
<option value="Reboot">Reboot</option>
<option value="Quit">Quit</option>
</options>
</command>
</channel-type>
<!-- Kodi variables -->
<channel-type id="pvr-channel" advanced="true">
<item-type>String</item-type>
<label>PVR Channel Title</label>
<description>Title of the current PVR channel</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="showtitle">
<item-type>String</item-type>
<label>Show Title</label>
<description>Title of the current show</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="album">
<item-type>String</item-type>
<label>Album</label>
<description>Album name of the current song</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="mediatype">
<item-type>String</item-type>
<label>Media Type</label>
<description>Media type of the current file</description>
<state readOnly="true" pattern="%s"></state>
</channel-type>
<channel-type id="genreList">
<item-type>String</item-type>
<label>Genres</label>
<description>Comma-separated list of genres of the current file</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="thumbnail">
<item-type>Image</item-type>
<label>Thumbnail</label>
<description>The current thumbnail</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="fanart">
<item-type>Image</item-type>
<label>Fanart</label>
<description>The current fanart</description>
<state readOnly="true"></state>
</channel-type>
<channel-type id="codec" advanced="true">
<item-type>String</item-type>
<label>Codec</label>
<description>Codec of currently playing media</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="currenttime">
<item-type>Number:Time</item-type>
<label>Current Time</label>
<description>Current time of currently playing media</description>
<state pattern="%d %unit%"/>
</channel-type>
<channel-type id="currenttimepercentage" advanced="true">
<item-type>Number:Dimensionless</item-type>
<label>Current Time in Percent</label>
<description>Current time of currently playing media</description>
<state readOnly="true" min="0" max="100" pattern="%d %unit%"/>
</channel-type>
<channel-type id="duration">
<item-type>Number:Time</item-type>
<label>Duration</label>
<description>Length of currently playing media</description>
<state readOnly="true" pattern="%d %unit%"/>
</channel-type>
<channel-type id="id" advanced="true">
<item-type>Number</item-type>
<label>Id</label>
<description>id in kodi database</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="enabled">
<item-type>Switch</item-type>
<label>Enabled</label>
<description>enabled</description>
</channel-type>
<channel-type id="channels" advanced="true">
<item-type>Number</item-type>
<label>Channels</label>
<description>Channels</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="width" advanced="true">
<item-type>Number</item-type>
<label>Video Width</label>
<description>Video Width</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="height" advanced="true">
<item-type>Number</item-type>
<label>Video Height</label>
<description>Video Height</description>
<state readOnly="true" pattern="%d"/>
</channel-type>
<channel-type id="index" advanced="true">
<item-type>Number</item-type>
<label>Index</label>
<description>stream index</description>
<state pattern="%d"/>
</channel-type>
<channel-type id="name" advanced="true">
<item-type>String</item-type>
<label>Name</label>
<description>Name</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="uniqueid" advanced="true">
<item-type>String</item-type>
<label>UniqueID</label>
<description>UniqueID</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="language" advanced="false">
<item-type>String</item-type>
<label>Language</label>
<description>stream Language</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="file" advanced="true">
<item-type>String</item-type>
<label>File</label>
<description>file path and name</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="rating" advanced="true">
<item-type>Number</item-type>
<label>Rating</label>
<description>Rating</description>
<state readOnly="true" pattern="%.1f"/>
</channel-type>
<channel-type id="mpaa" advanced="true">
<item-type>String</item-type>
<label>MPAA Rating</label>
<description>MPAA Rating</description>
<state readOnly="true" pattern="%s"/>
</channel-type>
<channel-type id="profile" advanced="true">
<item-type>String</item-type>
<label>Profile</label>
<description>Profile of Kodi</description>
<state pattern="%s"/>
</channel-type>
</thing:thing-descriptions>

View File

@@ -0,0 +1,179 @@
/**
* Copyright (c) 2010-2020 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.kodi.internal.utils;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.openhab.core.config.core.ConfigConstants;
/**
* Test class for the {@link ByteArrayFileCache} class.
*
* @author Christoph Weitkamp - Initial contribution
*/
public class ByteArrayFileCacheTest {
private static final String SERVICE_PID = "org.openhab.binding.kodi";
private static final File USERDATA_FOLDER = new File(ConfigConstants.getUserDataFolder());
private static final File CACHE_FOLDER = new File(USERDATA_FOLDER, ByteArrayFileCache.CACHE_FOLDER_NAME);
private static final File SERVICE_CACHE_FOLDER = new File(CACHE_FOLDER, SERVICE_PID);
private static final String MP3_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.mp3";
private static final String TXT_FILE_NAME = SERVICE_CACHE_FOLDER.getAbsolutePath() + "doorbell.txt";
private static final byte[] EMPTY_BUFFER = new byte[0];
private ByteArrayFileCache subject;
@Before
public void setUp() {
subject = new ByteArrayFileCache(SERVICE_PID);
}
@After
public void tearDown() {
// delete all files
subject.clear();
}
@AfterClass
public static void cleanUp() {
// delete all folders
SERVICE_CACHE_FOLDER.delete();
CACHE_FOLDER.delete();
USERDATA_FOLDER.delete();
}
@Test
public void testGetFileExtension() {
assertThat(subject.getFileExtension("/var/log/openhab2/"), is(nullValue()));
assertThat(subject.getFileExtension("/var/log/foo.bar/"), is(nullValue()));
assertThat(subject.getFileExtension("doorbell.mp3"), is(equalTo("mp3")));
assertThat(subject.getFileExtension("/tmp/doorbell.mp3"), is(equalTo("mp3")));
assertThat(subject.getFileExtension(MP3_FILE_NAME), is(equalTo("mp3")));
assertThat(subject.getFileExtension(TXT_FILE_NAME), is(equalTo("txt")));
assertThat(subject.getFileExtension("/var/log/openhab2/.."), is(""));
assertThat(subject.getFileExtension(".hidden"), is(equalTo("hidden")));
assertThat(subject.getFileExtension("C:\\Program Files (x86)\\java\\bin\\javaw.exe"), is(equalTo("exe")));
assertThat(subject.getFileExtension("https://www.youtube.com/watch?v=qYrpPrLY868"), is(nullValue()));
assertThat(subject.getFileExtension(
"a52fc16cca77ab2bf6abe51e463ce501.jpg?response-content-type=image%2Fjpeg&test=1"), is("jpg"));
}
@Test
public void testGetUniqueFileNameIsUnique() {
String mp3UniqueFileName = subject.getUniqueFileName(MP3_FILE_NAME);
assertThat(mp3UniqueFileName, is(equalTo(subject.getUniqueFileName(MP3_FILE_NAME))));
String txtUniqueFileName = subject.getUniqueFileName(TXT_FILE_NAME);
assertThat(txtUniqueFileName, is(equalTo(subject.getUniqueFileName(TXT_FILE_NAME))));
assertThat(mp3UniqueFileName, is(not(equalTo(txtUniqueFileName))));
}
@Test
public void testGet() {
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
}
@Test
public void testPut() throws IOException {
byte[] buffer = readFile();
subject.put(MP3_FILE_NAME, buffer);
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
}
@Test
public void testPutIfAbsent() throws IOException {
byte[] buffer = readFile();
subject.putIfAbsent(MP3_FILE_NAME, buffer);
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
}
@Test
public void testPutIfAbsentAndGet() throws IOException {
byte[] buffer = readFile();
assertThat(subject.putIfAbsentAndGet(MP3_FILE_NAME, buffer), is(equalTo(buffer)));
}
@Test
public void testContainsKey() throws IOException {
assertThat(subject.containsKey(MP3_FILE_NAME), is(false));
subject.put(MP3_FILE_NAME, readFile());
assertThat(subject.containsKey(MP3_FILE_NAME), is(true));
}
@Test
public void testRemove() throws IOException {
subject.put(MP3_FILE_NAME, readFile());
subject.remove(MP3_FILE_NAME);
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
}
@Test
public void testClear() throws IOException {
subject.put(MP3_FILE_NAME, readFile());
subject.clear();
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
}
@Test
public void clearExpiredClearsNothing() throws IOException {
byte[] buffer = readFile();
subject.put(MP3_FILE_NAME, buffer);
subject.clearExpired();
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(buffer)));
}
@Test
public void clearExpired() throws IOException {
subject = new ByteArrayFileCache(SERVICE_PID, 1);
subject.put(MP3_FILE_NAME, readFile());
// manipulate time of last use
File fileInCache = subject.getUniqueFile(MP3_FILE_NAME);
fileInCache.setLastModified(System.currentTimeMillis() - 2 * ByteArrayFileCache.ONE_DAY_IN_MILLIS);
subject.clearExpired();
assertThat(subject.get(MP3_FILE_NAME), is(equalTo(EMPTY_BUFFER)));
}
private byte[] readFile() throws IOException {
byte[] buffer;
try (InputStream is = ByteArrayFileCacheTest.class.getResourceAsStream("/sounds/doorbell.mp3")) {
buffer = new byte[is.available()];
is.read(buffer);
}
return buffer;
}
}