mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[icalendar] Add EventFilter for existing calendars (#8583)
This commit fixes #8022. Signed-off-by: Michael Wodniok <michi@noorganization.org>
This commit is contained in:
parent
fa9e3db34b
commit
7312890d44
@ -6,12 +6,16 @@ Furthermore it is possible to embed `command tags` in the calendar event descrip
|
||||
|
||||
## Supported Things
|
||||
|
||||
The only thing type is the calendar.
|
||||
It is based on a single iCalendar file.
|
||||
The primary thing type is the calendar.
|
||||
It is based on a single iCalendar file and implemented as bridge.
|
||||
There can be multiple things having different properties representing different calendars.
|
||||
|
||||
Each calendar can have event filters which allow to get multiple events, maybe filtered by additional criteria. Time based filtering is done by each event's start.
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
### Configuration for `calendar`
|
||||
|
||||
Each `calendar` thing requires the following configuration parameters:
|
||||
|
||||
| parameter name | description | optional |
|
||||
@ -23,13 +27,31 @@ Each `calendar` thing requires the following configuration parameters:
|
||||
| `maxSize` | The maximum size of the iCal-file in Mebibytes. | mandatory (default available) |
|
||||
| `authorizationCode` | The authorization code to permit the execution of embedded command tags. If set, the binding checks that the authorization code in the command tag matches before executing any commands. | optional |
|
||||
|
||||
### Configuration for `eventfilter`
|
||||
|
||||
Each `eventfilter` thing requires a bridge of type `calendar` and has following configuration options:
|
||||
|
||||
| parameter name | description | optional |
|
||||
|------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------|
|
||||
| `maxEvents` | The count of expected results. | mandatory |
|
||||
| `refreshTime` | The frequency in minutes the channels get refreshed. | mandatory (default available) |
|
||||
| `datetimeUnit` | A unit for time settings in this filter. Valid values: `MINUTE`, `HOUR`, `DAY` and `WEEK`. | optional (required for time-based filtering) |
|
||||
| `datetimeStart` | The start of the time frame where to search for events relative to current time. Combined with `datetimeUnit`. | optional |
|
||||
| `datetimeEnd` | The end of the time frame where to search for events relative to current time. Combined with `datetimeUnit`. The value must be greater than `datetimeStart` to get results. | optional |
|
||||
| `datetimeRound` | Whether to round the datetimes of start and end down to the earlier time unit. Example if set: current time is 13:00, timeunit is set to `DAY`. Resulting search will start and end at 0:00. | optional |
|
||||
| `textEventField` | A field to filter the events text-based. Valid values: `SUMMARY`, `DESCRIPTION`, `COMMENT`, `CONTACT` and `LOCATION` (as described in RFC 5545). | optional/required for text-based filtering |
|
||||
| `textEventValue` | The text to filter events with. | optional |
|
||||
| `textValueType` | The type of the text to filter with. Valid values: `TEXT` (field must contain value), `REGEX` (field must match value, completely, dot matches all, case insensetive). | optional/required for text-based filtering |
|
||||
|
||||
## Channels
|
||||
|
||||
The channels describe the current and the next forthcoming event.
|
||||
### Channels for `calendar`
|
||||
|
||||
The channels of `calendar` describe the current and the next forthcoming event.
|
||||
They are all read-only.
|
||||
|
||||
| Channel | Type | Description |
|
||||
|-------------------|-----------|--------------------------------------------------------------------------------|
|
||||
|-------------------|-----------|-------------------------------------------------------------------------------------|
|
||||
| current_presence | Switch | Current presence of an event, `ON` if there is currently an event, `OFF` otherwise |
|
||||
| current_title | String | Title of a currently present event |
|
||||
| current_start | DateTime | Start of a currently present event |
|
||||
@ -38,6 +60,28 @@ They are all read-only.
|
||||
| next_start | DateTime | Start of the next event |
|
||||
| next_end | DateTime | End of the next event |
|
||||
|
||||
### Channels for `eventfilter`
|
||||
|
||||
The channels of `eventfilter` are generated using following scheme, all are read-only.
|
||||
|
||||
| Channel-scheme | Type | Description |
|
||||
|---------------------|-----------|------------------------|
|
||||
| `result_<no>#begin` | DateTime | The begin of an event |
|
||||
| `result_<no>#end` | DateTime | The end of an event |
|
||||
| `result_<no>#title` | String | The title of an event |
|
||||
|
||||
The scheme replaces `<no>` by the results index, beginning at `0`. An `eventfilter` having `maxEvents` set to 3 will have following channels:
|
||||
|
||||
* `result_0#begin`
|
||||
* `result_0#end`
|
||||
* `result_0#title`
|
||||
* `result_1#begin`
|
||||
* `result_1#end`
|
||||
* `result_1#title`
|
||||
* `result_2#begin`
|
||||
* `result_2#end`
|
||||
* `result_2#title`
|
||||
|
||||
## Command Tags
|
||||
|
||||
Each calendar event may include one or more command tags in its description text.
|
||||
@ -76,7 +120,8 @@ The `Authorization_Code` may *optionally* be used as follows:
|
||||
All required information must be provided in the thing definition, either via UI or in the `.things` file..
|
||||
|
||||
```
|
||||
Thing icalendar:calendar:deadbeef "My calendar" @ "Internet" [ url="http://example.org/calendar.ical", refreshTime=60 ]
|
||||
Bridge icalendar:calendar:deadbeef "My calendar" @ "Internet" [ url="http://example.org/calendar.ical", refreshTime=60 ]
|
||||
Thing icalendar:eventfilter:feedd0d0 "Tomorrows events" (icalendar:calendar:deadbeef) [ maxEvents=1, datetimeUnit="DAY", datetimeStart=1, datetimeEnd=2, datetimeRound=true ]
|
||||
```
|
||||
|
||||
Link the channels as usual to items:
|
||||
@ -86,6 +131,8 @@ String current_event_name "current event [%s]" <calenda
|
||||
DateTime current_event_until "current until [%1$tT, %1$tY-%1$tm-%1$td]" <calendar> { channel="icalendar:calendar:deadbeef:current_end" }
|
||||
String next_event_name "next event [%s]" <calendar> { channel="icalendar:calendar:deadbeef:next_title" }
|
||||
DateTime next_event_at "next at [%1$tT, %1$tY-%1$tm-%1$td]" <calendar> { channel="icalendar:calendar:deadbeef:next_start" }
|
||||
String first_event_name_tomorrow "first event [%s]" <calendar> { channel="icalendar:eventfilter:feedd0d0:event_0#title" }
|
||||
DateTime first_event_at_tomorrow "first at [%1$tT, %1$tY-%1$tm-%1$td]" <calendar> { channel="icalendar:eventfilter:feedd0d0:event_0#begin" }
|
||||
```
|
||||
|
||||
Sitemap just showing the current event and the beginning of the next:
|
||||
@ -98,6 +145,10 @@ sitemap local label="My Calendar Sitemap" {
|
||||
Text item=next_event_name label="next event [%s]"
|
||||
Text item=next_event_at label="next at [%1$tT, %1$tY-%1$tm-%1$td]"
|
||||
}
|
||||
Frame label="tomorrow" {
|
||||
Text item=first_event_name_tomorrow
|
||||
Text item=first_event_at_tomorrow
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -114,3 +165,7 @@ Command tags in a calendar event (in the case that configuration parameter `auth
|
||||
BEGIN:Calendar_Test_Switch:ON
|
||||
END:Calendar_Test_Switch:OFF
|
||||
```
|
||||
|
||||
## Breaking changes
|
||||
|
||||
In OH3 `calendar` was changed from Thing to Bridge. You need to recreate calendars (or replace `Thing` by `Bridge` in your `.things` file).
|
||||
|
@ -14,6 +14,8 @@ package org.openhab.binding.icalendar.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelGroupTypeUID;
|
||||
import org.openhab.core.thing.type.ChannelTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link ICalendarBindingConstants} class defines common constants, which are
|
||||
@ -28,6 +30,7 @@ public class ICalendarBindingConstants {
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_CALENDAR = new ThingTypeUID(BINDING_ID, "calendar");
|
||||
public static final ThingTypeUID THING_TYPE_FILTERED_EVENTS = new ThingTypeUID(BINDING_ID, "eventfilter");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_CURRENT_EVENT_TITLE = "current_title";
|
||||
@ -40,4 +43,19 @@ public class ICalendarBindingConstants {
|
||||
|
||||
// additional constants
|
||||
public static final int HTTP_TIMEOUT_SECS = 60;
|
||||
public static final String DATETIME_UNIT_MINUTE = "minute";
|
||||
public static final String DATETIME_UNIT_HOUR = "hour";
|
||||
public static final String DATETIME_UNIT_DAY = "day";
|
||||
public static final String DATETIME_UNIT_WEEK = "week";
|
||||
|
||||
// specials for EventFilter
|
||||
public static final int DEFAULT_FILTER_REFRESH = 15;
|
||||
public static final String RESULT_GROUP_ID_PREFIX = "result_";
|
||||
public static final String RESULT_BEGIN_ID = "begin";
|
||||
public static final String RESULT_END_ID = "end";
|
||||
public static final String RESULT_TITLE_ID = "title";
|
||||
public static final ChannelGroupTypeUID GROUP_TYPE_UID = new ChannelGroupTypeUID(BINDING_ID, "result");
|
||||
public static final ChannelTypeUID BEGIN_TYPE_UID = new ChannelTypeUID(BINDING_ID, "result_start");
|
||||
public static final ChannelTypeUID END_TYPE_UID = new ChannelTypeUID(BINDING_ID, "result_end");
|
||||
public static final ChannelTypeUID TITLE_TYPE_UID = new ChannelTypeUID(BINDING_ID, "result_title");
|
||||
}
|
||||
|
@ -12,17 +12,22 @@
|
||||
*/
|
||||
package org.openhab.binding.icalendar.internal;
|
||||
|
||||
import static org.openhab.binding.icalendar.internal.ICalendarBindingConstants.THING_TYPE_CALENDAR;
|
||||
import static org.openhab.binding.icalendar.internal.ICalendarBindingConstants.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.openhab.binding.icalendar.internal.handler.EventFilterHandler;
|
||||
import org.openhab.binding.icalendar.internal.handler.ICalendarHandler;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||
@ -31,6 +36,8 @@ import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||
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 ICalendarHandlerFactory} is responsible for creating things and thing
|
||||
@ -38,21 +45,27 @@ import org.osgi.service.component.annotations.Reference;
|
||||
*
|
||||
* @author Michael Wodniok - Initial contribution
|
||||
* @author Andrew Fiddian-Green - EventPublisher code
|
||||
* @author Michael Wodniok - Added FilteredEvent item type/handler
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.icalendar", service = ThingHandlerFactory.class)
|
||||
public class ICalendarHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_CALENDAR);
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Stream
|
||||
.of(Collections.singleton(THING_TYPE_CALENDAR), Collections.singleton(THING_TYPE_FILTERED_EVENTS))
|
||||
.flatMap(Set::stream).collect(Collectors.toSet());
|
||||
private final Logger logger = LoggerFactory.getLogger(ICalendarHandlerFactory.class);
|
||||
|
||||
private final HttpClient sharedHttpClient;
|
||||
private final EventPublisher eventPublisher;
|
||||
private final TimeZoneProvider tzProvider;
|
||||
|
||||
@Activate
|
||||
public ICalendarHandlerFactory(@Reference HttpClientFactory httpClientFactory,
|
||||
@Reference EventPublisher eventPublisher) {
|
||||
@Reference EventPublisher eventPublisher, @Reference TimeZoneProvider tzProvider) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
sharedHttpClient = httpClientFactory.getCommonHttpClient();
|
||||
this.tzProvider = tzProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -67,6 +80,16 @@ public class ICalendarHandlerFactory extends BaseThingHandlerFactory {
|
||||
if (!supportsThingType(thingTypeUID)) {
|
||||
return null;
|
||||
}
|
||||
return new ICalendarHandler(thing, sharedHttpClient, eventPublisher);
|
||||
if (thingTypeUID.equals(THING_TYPE_CALENDAR)) {
|
||||
if (thing instanceof Bridge) {
|
||||
return new ICalendarHandler((Bridge) thing, sharedHttpClient, eventPublisher, tzProvider);
|
||||
} else {
|
||||
logger.warn(
|
||||
"The API of iCalendar has changed. You have to recreate the calendar according to the docs.");
|
||||
}
|
||||
} else if (thingTypeUID.equals(THING_TYPE_FILTERED_EVENTS)) {
|
||||
return new EventFilterHandler(thing, tzProvider);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -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.icalendar.internal.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The EventFilterConfiguration holds configuration for the Event Filter Item Type.
|
||||
*
|
||||
* @author Michael Wodniok - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EventFilterConfiguration {
|
||||
@Nullable
|
||||
public BigDecimal maxEvents;
|
||||
@Nullable
|
||||
public BigDecimal refreshTime;
|
||||
@Nullable
|
||||
public String datetimeUnit;
|
||||
@Nullable
|
||||
public BigDecimal datetimeStart;
|
||||
@Nullable
|
||||
public BigDecimal datetimeEnd;
|
||||
@Nullable
|
||||
public Boolean datetimeRound;
|
||||
@Nullable
|
||||
public String textEventField;
|
||||
@Nullable
|
||||
public String textEventValue;
|
||||
@Nullable
|
||||
public String textValueType;
|
||||
}
|
@ -14,17 +14,28 @@ package org.openhab.binding.icalendar.internal.config;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* The {@link ICalendarConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Michael Wodniok - Initial contribution
|
||||
* @author Andrew Fiddian-Green - Support for authorizationCode
|
||||
* @author Michael Wodniok - Added Nullable annotations for conformity
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ICalendarConfiguration {
|
||||
@Nullable
|
||||
public String authorizationCode;
|
||||
public Integer maxSize;
|
||||
@Nullable
|
||||
public BigDecimal maxSize;
|
||||
@Nullable
|
||||
public String password;
|
||||
@Nullable
|
||||
public BigDecimal refreshTime;
|
||||
@Nullable
|
||||
public String url;
|
||||
@Nullable
|
||||
public String username;
|
||||
}
|
||||
|
@ -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.icalendar.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Exception or semantically describe configuration errors. Message is meant to be shown to the user.
|
||||
*
|
||||
* @author Michael Wodniok - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConfigBrokenException extends Exception {
|
||||
private static final long serialVersionUID = -3805312008429711152L;
|
||||
|
||||
public ConfigBrokenException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -0,0 +1,396 @@
|
||||
/**
|
||||
* 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.icalendar.internal.handler;
|
||||
|
||||
import static org.openhab.binding.icalendar.internal.ICalendarBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.temporal.ChronoField;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.icalendar.internal.config.EventFilterConfiguration;
|
||||
import org.openhab.binding.icalendar.internal.handler.PullJob.CalendarUpdateListener;
|
||||
import org.openhab.binding.icalendar.internal.logic.AbstractPresentableCalendar;
|
||||
import org.openhab.binding.icalendar.internal.logic.Event;
|
||||
import org.openhab.binding.icalendar.internal.logic.EventTextFilter;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Channel;
|
||||
import org.openhab.core.thing.ChannelGroupUID;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerCallback;
|
||||
import org.openhab.core.thing.binding.builder.ChannelBuilder;
|
||||
import org.openhab.core.thing.binding.builder.ThingBuilder;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link EventFilterHandler} filters events from a calendar and presents them in a dynamic way.
|
||||
*
|
||||
* @author Michael Wodniok - Initial Contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EventFilterHandler extends BaseThingHandler implements CalendarUpdateListener {
|
||||
|
||||
private @Nullable EventFilterConfiguration configuration;
|
||||
private final Logger logger = LoggerFactory.getLogger(EventFilterHandler.class);
|
||||
private final List<ResultChannelSet> resultChannels;
|
||||
private final TimeZoneProvider tzProvider;
|
||||
private @Nullable ScheduledFuture<?> updateFuture;
|
||||
private boolean initFinished;
|
||||
|
||||
public EventFilterHandler(Thing thing, TimeZoneProvider tzProvider) {
|
||||
super(thing);
|
||||
resultChannels = new CopyOnWriteArrayList<>();
|
||||
initFinished = false;
|
||||
this.tzProvider = tzProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) {
|
||||
if (bridgeStatusInfo.getStatus() == ThingStatus.OFFLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
} else if (bridgeStatusInfo.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStates();
|
||||
} else {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
final ScheduledFuture<?> currentUpdateFuture = updateFuture;
|
||||
if (currentUpdateFuture != null) {
|
||||
currentUpdateFuture.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (command instanceof RefreshType) {
|
||||
if (initFinished) {
|
||||
updateStates();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.UNKNOWN);
|
||||
|
||||
Bridge iCalendarBridge = getBridge();
|
||||
if (iCalendarBridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"This thing requires a bridge configured to work.");
|
||||
return;
|
||||
}
|
||||
|
||||
final EventFilterConfiguration config = getConfigAs(EventFilterConfiguration.class);
|
||||
if (config.datetimeUnit == null && (config.datetimeEnd != null || config.datetimeStart != null)) {
|
||||
logger.warn("Start/End date-time is set but no unit. This will ignore the filter.");
|
||||
}
|
||||
if (config.textEventField != null && config.textValueType == null) {
|
||||
logger.warn("Event field is set but not match type. This will ignore the filter.");
|
||||
}
|
||||
configuration = config;
|
||||
|
||||
if (iCalendarBridge.getStatus() != ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
return;
|
||||
} else {
|
||||
updateChannelSet(config);
|
||||
updateStates();
|
||||
}
|
||||
initFinished = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCalendarUpdated() {
|
||||
updateStates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Consists of a set of channels and their group for describing a filtered event. *
|
||||
*/
|
||||
private class ResultChannelSet {
|
||||
ChannelGroupUID resultGroup;
|
||||
ChannelUID beginChannel;
|
||||
ChannelUID endChannel;
|
||||
ChannelUID titleChannel;
|
||||
|
||||
public ResultChannelSet(ChannelGroupUID group, ChannelUID begin, ChannelUID end, ChannelUID title) {
|
||||
resultGroup = group;
|
||||
beginChannel = begin;
|
||||
endChannel = end;
|
||||
titleChannel = title;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Describes some fixed time factors for unit selection.
|
||||
*/
|
||||
private enum TimeMultiplicator {
|
||||
MINUTE(60),
|
||||
HOUR(3600),
|
||||
DAY(86400),
|
||||
WEEK(604800);
|
||||
|
||||
private final int secondsPerUnit;
|
||||
|
||||
private TimeMultiplicator(int secondsPerUnit) {
|
||||
this.secondsPerUnit = secondsPerUnit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the count of seconds per unit.
|
||||
*
|
||||
* @return Seconds per unit.
|
||||
*/
|
||||
public int getMultiplier() {
|
||||
return secondsPerUnit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of channel sets according to the required amount.
|
||||
*
|
||||
* @param resultCount The required amount of results.
|
||||
*/
|
||||
private void generateExpectedChannelList(int resultCount) {
|
||||
synchronized (resultChannels) {
|
||||
if (resultChannels.size() == resultCount) {
|
||||
return;
|
||||
}
|
||||
resultChannels.clear();
|
||||
for (int position = 0; position < resultCount; position++) {
|
||||
ChannelGroupUID currentGroup = new ChannelGroupUID(getThing().getUID(),
|
||||
RESULT_GROUP_ID_PREFIX + position);
|
||||
ResultChannelSet current = new ResultChannelSet(currentGroup,
|
||||
new ChannelUID(currentGroup, RESULT_BEGIN_ID), new ChannelUID(currentGroup, RESULT_END_ID),
|
||||
new ChannelUID(currentGroup, RESULT_TITLE_ID));
|
||||
resultChannels.add(current);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks existing channels, adds missing and removes extraneous channels from the Thing.
|
||||
*
|
||||
* @param config The validated Configuration of the Thing.
|
||||
*/
|
||||
private void updateChannelSet(EventFilterConfiguration config) {
|
||||
final ThingHandlerCallback handlerCallback = getCallback();
|
||||
if (handlerCallback == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Channel> currentChannels = getThing().getChannels();
|
||||
final ThingBuilder thingBuilder = editThing();
|
||||
BigDecimal maxEvents = config.maxEvents;
|
||||
if (maxEvents == null || maxEvents.compareTo(BigDecimal.ZERO) < 1) {
|
||||
thingBuilder.withoutChannels(currentChannels);
|
||||
updateThing(thingBuilder.build());
|
||||
return;
|
||||
}
|
||||
generateExpectedChannelList(maxEvents.intValue());
|
||||
|
||||
synchronized (resultChannels) {
|
||||
currentChannels.stream().filter((Channel current) -> {
|
||||
String currentGroupId = current.getUID().getGroupId();
|
||||
if (currentGroupId == null) {
|
||||
return true;
|
||||
}
|
||||
for (ResultChannelSet channelSet : resultChannels) {
|
||||
if (channelSet.resultGroup.getId().contentEquals(currentGroupId)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}).forEach((Channel toDelete) -> {
|
||||
thingBuilder.withoutChannel(toDelete.getUID());
|
||||
});
|
||||
|
||||
resultChannels.stream().filter((ResultChannelSet current) -> {
|
||||
return (getThing().getChannelsOfGroup(current.resultGroup.toString()).size() == 0);
|
||||
}).forEach((ResultChannelSet current) -> {
|
||||
for (ChannelBuilder builder : handlerCallback.createChannelBuilders(current.resultGroup,
|
||||
GROUP_TYPE_UID)) {
|
||||
Channel currentChannel = builder.build();
|
||||
Channel existingChannel = getThing().getChannel(currentChannel.getUID());
|
||||
if (existingChannel == null) {
|
||||
thingBuilder.withChannel(currentChannel);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
updateThing(thingBuilder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates all states and channels. Reschedules an update if no error occurs.
|
||||
*/
|
||||
private void updateStates() {
|
||||
final Bridge iCalendarBridge = getBridge();
|
||||
if (iCalendarBridge == null) {
|
||||
logger.debug("Bridge not instantiated!");
|
||||
return;
|
||||
}
|
||||
final ICalendarHandler iCalendarHandler = (ICalendarHandler) iCalendarBridge.getHandler();
|
||||
if (iCalendarHandler == null) {
|
||||
logger.debug("ICalendarHandler not instantiated!");
|
||||
return;
|
||||
}
|
||||
final EventFilterConfiguration config = configuration;
|
||||
if (config == null) {
|
||||
logger.debug("Configuration not instantiated!");
|
||||
return;
|
||||
}
|
||||
final AbstractPresentableCalendar cal = iCalendarHandler.getRuntimeCalendar();
|
||||
if (cal != null) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
Instant reference = Instant.now();
|
||||
TimeMultiplicator multiplicator = null;
|
||||
EventTextFilter filter = null;
|
||||
int maxEvents;
|
||||
Instant begin = Instant.EPOCH;
|
||||
Instant end = Instant.ofEpochMilli(Long.MAX_VALUE);
|
||||
|
||||
try {
|
||||
String textFilterValue = config.textEventValue;
|
||||
if (textFilterValue != null) {
|
||||
String textEventField = config.textEventField;
|
||||
String textValueType = config.textValueType;
|
||||
if (textEventField == null || textValueType == null) {
|
||||
throw new ConfigBrokenException("Text filter settings are not set properly.");
|
||||
}
|
||||
try {
|
||||
EventTextFilter.Field textFilterField = EventTextFilter.Field.valueOf(textEventField);
|
||||
EventTextFilter.Type textFilterType = EventTextFilter.Type.valueOf(textValueType);
|
||||
|
||||
filter = new EventTextFilter(textFilterField, textFilterValue, textFilterType);
|
||||
} catch (IllegalArgumentException e2) {
|
||||
throw new ConfigBrokenException("textEventField or textValueType are not set properly.");
|
||||
}
|
||||
}
|
||||
|
||||
BigDecimal maxEventsBD = config.maxEvents;
|
||||
if (maxEventsBD == null) {
|
||||
throw new ConfigBrokenException("maxEvents is not set.");
|
||||
}
|
||||
maxEvents = maxEventsBD.intValue();
|
||||
if (maxEvents < 0) {
|
||||
throw new ConfigBrokenException("maxEvents is less than 0. This is not allowed.");
|
||||
}
|
||||
|
||||
try {
|
||||
final String datetimeUnit = config.datetimeUnit;
|
||||
if (datetimeUnit != null) {
|
||||
multiplicator = TimeMultiplicator.valueOf(datetimeUnit);
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new ConfigBrokenException("datetimeUnit is not set properly.");
|
||||
}
|
||||
|
||||
final Boolean datetimeRound = config.datetimeRound;
|
||||
if (datetimeRound != null && datetimeRound.booleanValue()) {
|
||||
if (multiplicator == null) {
|
||||
throw new ConfigBrokenException("datetimeUnit is missing but required for datetimeRound.");
|
||||
}
|
||||
ZonedDateTime refDT = reference.atZone(tzProvider.getTimeZone());
|
||||
switch (multiplicator) {
|
||||
case WEEK:
|
||||
refDT = refDT.with(ChronoField.DAY_OF_WEEK, 1);
|
||||
case DAY:
|
||||
refDT = refDT.with(ChronoField.HOUR_OF_DAY, 0);
|
||||
case HOUR:
|
||||
refDT = refDT.with(ChronoField.MINUTE_OF_HOUR, 0);
|
||||
case MINUTE:
|
||||
refDT = refDT.with(ChronoField.SECOND_OF_MINUTE, 0);
|
||||
}
|
||||
reference = refDT.toInstant();
|
||||
}
|
||||
|
||||
BigDecimal datetimeStart = config.datetimeStart;
|
||||
if (datetimeStart != null) {
|
||||
if (multiplicator == null) {
|
||||
throw new ConfigBrokenException("datetimeUnit is missing but required for datetimeStart.");
|
||||
}
|
||||
begin = reference.plusSeconds(datetimeStart.longValue() * multiplicator.getMultiplier());
|
||||
}
|
||||
BigDecimal datetimeEnd = config.datetimeEnd;
|
||||
if (datetimeEnd != null) {
|
||||
if (multiplicator == null) {
|
||||
throw new ConfigBrokenException("datetimeUnit is missing but required for datetimeEnd.");
|
||||
}
|
||||
end = reference.plusSeconds(datetimeEnd.longValue() * multiplicator.getMultiplier());
|
||||
}
|
||||
} catch (ConfigBrokenException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (resultChannels) {
|
||||
List<Event> results = cal.getFilteredEventsBetween(begin, end, filter, maxEvents);
|
||||
for (int position = 0; position < resultChannels.size(); position++) {
|
||||
ResultChannelSet channels = resultChannels.get(position);
|
||||
if (position < results.size()) {
|
||||
Event result = results.get(position);
|
||||
updateState(channels.titleChannel, new StringType(result.title));
|
||||
updateState(channels.beginChannel,
|
||||
new DateTimeType(result.start.atZone(tzProvider.getTimeZone())));
|
||||
updateState(channels.endChannel, new DateTimeType(result.end.atZone(tzProvider.getTimeZone())));
|
||||
} else {
|
||||
updateState(channels.titleChannel, UnDefType.UNDEF);
|
||||
updateState(channels.beginChannel, UnDefType.UNDEF);
|
||||
updateState(channels.endChannel, UnDefType.UNDEF);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Calendar has not been retrieved yet.");
|
||||
}
|
||||
|
||||
int refreshTime = DEFAULT_FILTER_REFRESH;
|
||||
if (config.refreshTime != null) {
|
||||
refreshTime = config.refreshTime.intValue();
|
||||
if (refreshTime < 1) {
|
||||
logger.debug("refreshTime is set to invalid value. Using default.");
|
||||
refreshTime = DEFAULT_FILTER_REFRESH;
|
||||
}
|
||||
}
|
||||
ScheduledFuture<?> currentUpdateFuture = updateFuture;
|
||||
if (currentUpdateFuture != null) {
|
||||
currentUpdateFuture.cancel(true);
|
||||
}
|
||||
updateFuture = scheduler.scheduleWithFixedDelay(this::updateStates, refreshTime, refreshTime, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
@ -17,10 +17,10 @@ import static org.openhab.binding.icalendar.internal.ICalendarBindingConstants.*
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneId;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -37,15 +37,18 @@ import org.openhab.binding.icalendar.internal.logic.CommandTagType;
|
||||
import org.openhab.binding.icalendar.internal.logic.Event;
|
||||
import org.openhab.core.OpenHAB;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.i18n.TimeZoneProvider;
|
||||
import org.openhab.core.items.events.ItemEventFactory;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
import org.openhab.core.thing.ThingStatusDetail;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.openhab.core.types.UnDefType;
|
||||
@ -60,25 +63,28 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Andrew Fiddian-Green - Support for Command Tags embedded in the Event description
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ICalendarHandler extends BaseThingHandler implements CalendarUpdateListener {
|
||||
public class ICalendarHandler extends BaseBridgeHandler implements CalendarUpdateListener {
|
||||
|
||||
private final File calendarFile;
|
||||
private @Nullable ICalendarConfiguration configuration;
|
||||
private final EventPublisher eventPublisherCallback;
|
||||
private final HttpClient httpClient;
|
||||
private final Logger logger = LoggerFactory.getLogger(ICalendarHandler.class);
|
||||
private final TimeZoneProvider tzProvider;
|
||||
private @Nullable ScheduledFuture<?> pullJobFuture;
|
||||
private @Nullable AbstractPresentableCalendar runtimeCalendar;
|
||||
private @Nullable ScheduledFuture<?> updateJobFuture;
|
||||
private Instant updateStatesLastCalledTime;
|
||||
|
||||
public ICalendarHandler(Thing thing, HttpClient httpClient, EventPublisher eventPublisher) {
|
||||
super(thing);
|
||||
public ICalendarHandler(Bridge bridge, HttpClient httpClient, EventPublisher eventPublisher,
|
||||
TimeZoneProvider tzProvider) {
|
||||
super(bridge);
|
||||
this.httpClient = httpClient;
|
||||
calendarFile = new File(OpenHAB.getUserDataFolder() + File.separator
|
||||
+ getThing().getUID().getAsString().replaceAll("[<>:\"/\\\\|?*]", "_") + ".ical");
|
||||
eventPublisherCallback = eventPublisher;
|
||||
updateStatesLastCalledTime = Instant.now();
|
||||
this.tzProvider = tzProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -119,25 +125,34 @@ public class ICalendarHandler extends BaseThingHandler implements CalendarUpdate
|
||||
final ICalendarConfiguration currentConfiguration = getConfigAs(ICalendarConfiguration.class);
|
||||
configuration = currentConfiguration;
|
||||
|
||||
try {
|
||||
if ((currentConfiguration.username == null && currentConfiguration.password != null)
|
||||
|| (currentConfiguration.username != null && currentConfiguration.password == null)) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Only one of username and password was set. This is invalid.");
|
||||
return;
|
||||
throw new ConfigBrokenException("Only one of username and password was set. This is invalid.");
|
||||
}
|
||||
|
||||
PullJob regularPull;
|
||||
final BigDecimal maxSizeBD = currentConfiguration.maxSize;
|
||||
if (maxSizeBD == null || maxSizeBD.intValue() < 1) {
|
||||
throw new ConfigBrokenException(
|
||||
"maxSize is either not set or less than 1 (mebibyte), which is not allowed.");
|
||||
}
|
||||
final int maxSize = maxSizeBD.intValue();
|
||||
try {
|
||||
regularPull = new PullJob(httpClient, new URI(currentConfiguration.url), currentConfiguration.username,
|
||||
currentConfiguration.password, calendarFile, currentConfiguration.maxSize * 1048576, this);
|
||||
currentConfiguration.password, calendarFile, maxSize * 1048576, this);
|
||||
} catch (URISyntaxException e) {
|
||||
logger.warn(
|
||||
"The URI '{}' for downloading the calendar contains syntax errors. This will result in no downloads/updates.",
|
||||
currentConfiguration.url, e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
|
||||
return;
|
||||
throw new ConfigBrokenException(String.format(
|
||||
"The URI '%s' for downloading the calendar contains syntax errors.", currentConfiguration.url));
|
||||
|
||||
}
|
||||
|
||||
final BigDecimal refreshTimeBD = currentConfiguration.refreshTime;
|
||||
if (refreshTimeBD == null || refreshTimeBD.longValue() < 1) {
|
||||
throw new ConfigBrokenException(
|
||||
"refreshTime is either not set or less than 1 (minute), which is not allowed.");
|
||||
}
|
||||
final long refreshTime = refreshTimeBD.longValue();
|
||||
if (calendarFile.isFile()) {
|
||||
if (reloadCalendar()) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
@ -147,14 +162,16 @@ public class ICalendarHandler extends BaseThingHandler implements CalendarUpdate
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"The calendar seems to be configured correctly, but the local copy of calendar could not be loaded.");
|
||||
}
|
||||
pullJobFuture = scheduler.scheduleWithFixedDelay(regularPull, currentConfiguration.refreshTime.longValue(),
|
||||
currentConfiguration.refreshTime.longValue(), TimeUnit.MINUTES);
|
||||
pullJobFuture = scheduler.scheduleWithFixedDelay(regularPull, refreshTime, refreshTime,
|
||||
TimeUnit.MINUTES);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE);
|
||||
logger.debug(
|
||||
"The calendar is currently offline as no local copy exists. It will go online as soon as a valid valid calendar is retrieved.");
|
||||
pullJobFuture = scheduler.scheduleWithFixedDelay(regularPull, 0,
|
||||
currentConfiguration.refreshTime.longValue(), TimeUnit.MINUTES);
|
||||
pullJobFuture = scheduler.scheduleWithFixedDelay(regularPull, 0, refreshTime, TimeUnit.MINUTES);
|
||||
}
|
||||
} catch (ConfigBrokenException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,20 +179,36 @@ public class ICalendarHandler extends BaseThingHandler implements CalendarUpdate
|
||||
public void onCalendarUpdated() {
|
||||
if (reloadCalendar()) {
|
||||
updateStates();
|
||||
for (Thing childThing : getThing().getThings()) {
|
||||
ThingHandler handler = childThing.getHandler();
|
||||
if (handler instanceof CalendarUpdateListener) {
|
||||
try {
|
||||
((CalendarUpdateListener) handler).onCalendarUpdated();
|
||||
} catch (Exception e) {
|
||||
logger.trace("The update of a child handler failed. Ignoring.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.trace("Calendar was updated, but loading failed.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the calendar that is used for all operations
|
||||
*/
|
||||
@Nullable
|
||||
public AbstractPresentableCalendar getRuntimeCalendar() {
|
||||
return runtimeCalendar;
|
||||
}
|
||||
|
||||
private void executeEventCommands(List<Event> events, CommandTagType execTime) {
|
||||
// no begun or ended events => exit quietly as there is nothing to do
|
||||
if (events.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// prevent potential synchronization issues (MVN null pointer warnings) in "configuration"
|
||||
@Nullable
|
||||
ICalendarConfiguration syncConfiguration = configuration;
|
||||
final ICalendarConfiguration syncConfiguration = configuration;
|
||||
if (syncConfiguration == null) {
|
||||
logger.debug("Configuration not instantiated!");
|
||||
return;
|
||||
@ -318,9 +351,9 @@ public class ICalendarHandler extends BaseThingHandler implements CalendarUpdate
|
||||
} else {
|
||||
updateState(CHANNEL_CURRENT_EVENT_TITLE, new StringType(currentEvent.title));
|
||||
updateState(CHANNEL_CURRENT_EVENT_START,
|
||||
new DateTimeType(currentEvent.start.atZone(ZoneId.systemDefault())));
|
||||
new DateTimeType(currentEvent.start.atZone(tzProvider.getTimeZone())));
|
||||
updateState(CHANNEL_CURRENT_EVENT_END,
|
||||
new DateTimeType(currentEvent.end.atZone(ZoneId.systemDefault())));
|
||||
new DateTimeType(currentEvent.end.atZone(tzProvider.getTimeZone())));
|
||||
}
|
||||
} else {
|
||||
updateState(CHANNEL_CURRENT_EVENT_PRESENT, OnOffType.OFF);
|
||||
@ -332,8 +365,9 @@ public class ICalendarHandler extends BaseThingHandler implements CalendarUpdate
|
||||
final Event nextEvent = calendar.getNextEvent(now);
|
||||
if (nextEvent != null) {
|
||||
updateState(CHANNEL_NEXT_EVENT_TITLE, new StringType(nextEvent.title));
|
||||
updateState(CHANNEL_NEXT_EVENT_START, new DateTimeType(nextEvent.start.atZone(ZoneId.systemDefault())));
|
||||
updateState(CHANNEL_NEXT_EVENT_END, new DateTimeType(nextEvent.end.atZone(ZoneId.systemDefault())));
|
||||
updateState(CHANNEL_NEXT_EVENT_START,
|
||||
new DateTimeType(nextEvent.start.atZone(tzProvider.getTimeZone())));
|
||||
updateState(CHANNEL_NEXT_EVENT_END, new DateTimeType(nextEvent.end.atZone(tzProvider.getTimeZone())));
|
||||
} else {
|
||||
updateState(CHANNEL_NEXT_EVENT_TITLE, UnDefType.UNDEF);
|
||||
updateState(CHANNEL_NEXT_EVENT_START, UnDefType.UNDEF);
|
||||
|
@ -53,7 +53,7 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class PullJob implements Runnable {
|
||||
private final static String TMP_FILE_PREFIX = "icalendardld";
|
||||
private static final String TMP_FILE_PREFIX = "icalendardld";
|
||||
|
||||
private final Authentication.@Nullable Result authentication;
|
||||
private final File destination;
|
||||
@ -91,7 +91,7 @@ class PullJob implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
final Request request = httpClient.newRequest(sourceURI).followRedirects(true).method(HttpMethod.GET);
|
||||
final Authentication.@Nullable Result currentAuthentication = authentication;
|
||||
final Authentication.Result currentAuthentication = authentication;
|
||||
if (currentAuthentication != null) {
|
||||
currentAuthentication.apply(request);
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
*
|
||||
* @author Michael Wodniok - Initial contribution
|
||||
* @author Andrew Fiddian-Green - Methods getJustBegunEvents() & getJustEndedEvents()
|
||||
* @author Michael Wodniok - Added getFilteredEventsBetween()
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractPresentableCalendar {
|
||||
@ -86,4 +87,16 @@ public abstract class AbstractPresentableCalendar {
|
||||
* @return True if an event is present.
|
||||
*/
|
||||
public abstract boolean isEventPresent(Instant instant);
|
||||
|
||||
/**
|
||||
* Return a filtered List of events with a maximum count, ordered by start.
|
||||
*
|
||||
* @param begin The begin of the time range where to search for events
|
||||
* @param end The end of the time range where to search for events
|
||||
* @param filter A filter for contents, if set to null, all events will be returned
|
||||
* @param maximumCount The maximum of events returned here.
|
||||
* @return A list with the filtered results.
|
||||
*/
|
||||
public abstract List<Event> getFilteredEventsBetween(Instant begin, Instant end, @Nullable EventTextFilter filter,
|
||||
int maximumCount);
|
||||
}
|
||||
|
@ -18,24 +18,32 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.icalendar.internal.logic.EventTextFilter.Type;
|
||||
|
||||
import biweekly.ICalendar;
|
||||
import biweekly.component.VEvent;
|
||||
import biweekly.io.TimezoneAssignment;
|
||||
import biweekly.io.TimezoneInfo;
|
||||
import biweekly.io.text.ICalReader;
|
||||
import biweekly.property.Comment;
|
||||
import biweekly.property.Contact;
|
||||
import biweekly.property.DateEnd;
|
||||
import biweekly.property.DateStart;
|
||||
import biweekly.property.Description;
|
||||
import biweekly.property.DurationProperty;
|
||||
import biweekly.property.Location;
|
||||
import biweekly.property.Status;
|
||||
import biweekly.property.Summary;
|
||||
import biweekly.property.TextProperty;
|
||||
import biweekly.property.Uid;
|
||||
import biweekly.util.com.google.ical.compat.javautil.DateIterator;
|
||||
|
||||
@ -46,6 +54,7 @@ import biweekly.util.com.google.ical.compat.javautil.DateIterator;
|
||||
*
|
||||
* @author Michael Wodniok - Initial contribution
|
||||
* @author Andrew Fiddian-Green - Methods getJustBegunEvents() & getJustEndedEvents()
|
||||
* @author Michael Wodniok - Extension for filtered events
|
||||
*/
|
||||
@NonNullByDefault
|
||||
class BiweeklyPresentableCalendar extends AbstractPresentableCalendar {
|
||||
@ -140,7 +149,6 @@ class BiweeklyPresentableCalendar extends AbstractPresentableCalendar {
|
||||
while (startDates.hasNext()) {
|
||||
final Instant startInstant = startDates.next().toInstant();
|
||||
if (startInstant.isAfter(instant)) {
|
||||
@Nullable
|
||||
final Uid currentEventUid = currentEvent.getUid();
|
||||
if (currentEventUid == null || !isCounteredBy(startInstant, currentEventUid, negativeEvents)) {
|
||||
candidates.add(new VEventWPeriod(currentEvent, startInstant, startInstant.plus(duration)));
|
||||
@ -167,6 +175,104 @@ class BiweeklyPresentableCalendar extends AbstractPresentableCalendar {
|
||||
return (this.getCurrentComponentWPeriod(instant) != null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Event> getFilteredEventsBetween(Instant begin, Instant end, @Nullable EventTextFilter filter,
|
||||
int maximumCount) {
|
||||
List<VEventWPeriod> candidates = this.getVEventWPeriodsBetween(begin, end);
|
||||
final List<Event> results = new ArrayList<>(candidates.size());
|
||||
|
||||
if (filter != null) {
|
||||
Pattern filterPattern;
|
||||
if (filter.type == Type.TEXT) {
|
||||
filterPattern = Pattern.compile(".*" + Pattern.quote(filter.value) + ".*",
|
||||
Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
|
||||
} else {
|
||||
filterPattern = Pattern.compile(filter.value);
|
||||
}
|
||||
|
||||
Class<? extends TextProperty> propertyClass;
|
||||
switch (filter.field) {
|
||||
case SUMMARY:
|
||||
propertyClass = Summary.class;
|
||||
break;
|
||||
case COMMENT:
|
||||
propertyClass = Comment.class;
|
||||
break;
|
||||
case CONTACT:
|
||||
propertyClass = Contact.class;
|
||||
break;
|
||||
case DESCRIPTION:
|
||||
propertyClass = Description.class;
|
||||
break;
|
||||
case LOCATION:
|
||||
propertyClass = Location.class;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown Property to filter for.");
|
||||
}
|
||||
|
||||
List<VEventWPeriod> filteredCandidates = candidates.stream().filter(current -> {
|
||||
List<? extends TextProperty> properties = current.vEvent.getProperties(propertyClass);
|
||||
for (TextProperty prop : properties) {
|
||||
if (filterPattern.matcher(prop.getValue()).matches()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}).collect(Collectors.toList());
|
||||
candidates = filteredCandidates;
|
||||
}
|
||||
|
||||
for (VEventWPeriod eventWPeriod : candidates) {
|
||||
results.add(eventWPeriod.toEvent());
|
||||
}
|
||||
|
||||
Collections.sort(results);
|
||||
|
||||
return results.subList(0, (maximumCount > results.size() ? results.size() : maximumCount));
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds events which begin in the given frame.
|
||||
*
|
||||
* @param frameBegin Begin of the frame where to search events.
|
||||
* @param frameEnd End of the time frame where to search events.
|
||||
* @return All events which begin in the time frame.
|
||||
*/
|
||||
private List<VEventWPeriod> getVEventWPeriodsBetween(Instant frameBegin, Instant frameEnd) {
|
||||
final List<VEvent> positiveEvents = new ArrayList<>();
|
||||
final List<VEvent> negativeEvents = new ArrayList<>();
|
||||
classifyEvents(positiveEvents, negativeEvents);
|
||||
|
||||
final List<VEventWPeriod> eventList = new ArrayList<>();
|
||||
for (final VEvent positiveEvent : positiveEvents) {
|
||||
final DateIterator positiveBeginDates = getRecurredEventDateIterator(positiveEvent);
|
||||
positiveBeginDates.advanceTo(Date.from(frameBegin));
|
||||
while (positiveBeginDates.hasNext()) {
|
||||
final Instant begInst = positiveBeginDates.next().toInstant();
|
||||
if (begInst.isAfter(frameEnd)) {
|
||||
break;
|
||||
}
|
||||
Duration duration = getEventLength(positiveEvent);
|
||||
if (duration == null) {
|
||||
duration = Duration.ZERO;
|
||||
}
|
||||
|
||||
final VEventWPeriod resultingVEWP = new VEventWPeriod(positiveEvent, begInst, begInst.plus(duration));
|
||||
final Uid eventUid = positiveEvent.getUid();
|
||||
if (eventUid != null) {
|
||||
if (!isCounteredBy(begInst, eventUid, negativeEvents)) {
|
||||
eventList.add(resultingVEWP);
|
||||
}
|
||||
} else {
|
||||
eventList.add(resultingVEWP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return eventList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classifies events into positive and negative ones.
|
||||
*
|
||||
@ -175,7 +281,6 @@ class BiweeklyPresentableCalendar extends AbstractPresentableCalendar {
|
||||
*/
|
||||
private void classifyEvents(Collection<VEvent> positiveEvents, Collection<VEvent> negativeEvents) {
|
||||
for (final VEvent currentEvent : usedCalendar.getEvents()) {
|
||||
@Nullable
|
||||
final Status eventStatus = currentEvent.getStatus();
|
||||
boolean positive = (eventStatus == null || (eventStatus.isTentative() || eventStatus.isConfirmed()));
|
||||
final Collection<VEvent> positiveOrNegativeEvents = (positive ? positiveEvents : negativeEvents);
|
||||
@ -205,7 +310,6 @@ class BiweeklyPresentableCalendar extends AbstractPresentableCalendar {
|
||||
final Instant startInstant = startDates.next().toInstant();
|
||||
final Instant endInstant = startInstant.plus(duration);
|
||||
if (startInstant.isBefore(instant) && endInstant.isAfter(instant)) {
|
||||
@Nullable
|
||||
final Uid eventUid = currentEvent.getUid();
|
||||
if (eventUid == null || !isCounteredBy(startInstant, eventUid, negativeEvents)) {
|
||||
return new VEventWPeriod(currentEvent, startInstant, endInstant);
|
||||
@ -270,7 +374,6 @@ class BiweeklyPresentableCalendar extends AbstractPresentableCalendar {
|
||||
*/
|
||||
private boolean isCounteredBy(Instant startInstant, Uid eventUid, Collection<VEvent> counterEvents) {
|
||||
for (final VEvent counterEvent : counterEvents) {
|
||||
@Nullable
|
||||
final Uid counterEventUid = counterEvent.getUid();
|
||||
if (counterEventUid != null && eventUid.getValue().contentEquals(counterEventUid.getValue())) {
|
||||
final DateIterator counterStartDates = getRecurredEventDateIterator(counterEvent);
|
||||
|
@ -26,7 +26,7 @@ import org.eclipse.jdt.annotation.Nullable;
|
||||
* @author Andrew Fiddian-Green - Added support for event description
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Event {
|
||||
public class Event implements Comparable<Event> {
|
||||
public final List<CommandTag> commandTags = new ArrayList<CommandTag>();
|
||||
public final Instant end;
|
||||
public final Instant start;
|
||||
@ -50,6 +50,16 @@ public class Event {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String[] tagStrings = new String[this.commandTags.size()];
|
||||
for (int i = 0; i < tagStrings.length; i++) {
|
||||
tagStrings[i] = this.commandTags.get(i).toString();
|
||||
}
|
||||
return "Event(title: " + this.title + ", start: " + this.start.toString() + ", end: " + this.end.toString()
|
||||
+ ", commandTags: List(" + String.join(", ", tagStrings) + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (other == null || other.getClass() != this.getClass()) {
|
||||
@ -59,4 +69,9 @@ public class Event {
|
||||
return (this.title.equals(otherEvent.title) && this.start.equals(otherEvent.start)
|
||||
&& this.end.equals(otherEvent.end));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Event o) {
|
||||
return start.compareTo(o.start);
|
||||
}
|
||||
}
|
||||
|
@ -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.icalendar.internal.logic;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Transport class for a simple text filter.
|
||||
*
|
||||
* @author Michael Wodniok - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class EventTextFilter {
|
||||
public static enum Type {
|
||||
TEXT,
|
||||
REGEX
|
||||
}
|
||||
|
||||
public static enum Field {
|
||||
SUMMARY,
|
||||
DESCRIPTION,
|
||||
COMMENT,
|
||||
CONTACT,
|
||||
LOCATION
|
||||
}
|
||||
|
||||
public Field field;
|
||||
public String value;
|
||||
public Type type;
|
||||
|
||||
public EventTextFilter(Field field, String value, Type type) {
|
||||
this.field = field;
|
||||
this.value = value;
|
||||
this.type = type;
|
||||
}
|
||||
}
|
@ -5,6 +5,8 @@ binding.icalendar.description = Binding zur Nutzung von iCal-Kalendern als Pr
|
||||
# thing types
|
||||
thing-type.icalendar.calendar.label = Kalender
|
||||
thing-type.icalendar.calendar.description = Kalender basierend auf einem lesbaren iCal-Kalender.
|
||||
thing-type.icalendar.eventfilter.label = Eintragsfilter
|
||||
thing-type.icalendar.eventfilter.description = Gefilterte Events aus dem zugeordneten Kalender.
|
||||
|
||||
# thing type config description
|
||||
thing-type.config.icalendar.calendar.url.label = URL
|
||||
@ -19,6 +21,35 @@ thing-type.config.icalendar.calendar.maxSize.label = Maximale Gr
|
||||
thing-type.config.icalendar.calendar.maxSize.description = Es werden nur iCal-Dateien verwendet, die bis zur angegebenen Größe (in Mebibytes) groß sind
|
||||
thing-type.config.icalendar.calendar.authorizationCode.label = Autorisierungs-Code
|
||||
thing-type.config.icalendar.calendar.authorizationCode.description = Code zur Autorisierung von Kommandos in Kalendareinträgen
|
||||
thing-type.config.icalendar.eventfilter.maxEvents.label = Ergebnis-Maximum
|
||||
thing-type.config.icalendar.eventfilter.maxEvents.description = Maximale Anzahl an Ergebnissen dieses Filters
|
||||
thing-type.config.icalendar.eventfilter.refreshTime.label = Aktualisierungsintervall
|
||||
thing-type.config.icalendar.eventfilter.refreshTime.description = Intervall, in dem die Ergebnisliste aktualisiert wird (Minuten)
|
||||
thing-type.config.icalendar.eventfilter.datetimeUnit.label = Zeiteinheit
|
||||
thing-type.config.icalendar.eventfilter.datetimeUnit.description = Einheit der Angaben zu Start und Ende
|
||||
thing-type.config.icalendar.eventfilter.datetimeUnit.option.MINUTE = Minute
|
||||
thing-type.config.icalendar.eventfilter.datetimeUnit.option.HOUR = Stunde
|
||||
thing-type.config.icalendar.eventfilter.datetimeUnit.option.DAY = Tag
|
||||
thing-type.config.icalendar.eventfilter.datetimeUnit.option.WEEK = Woche
|
||||
thing-type.config.icalendar.eventfilter.datetimeStart.label = Start
|
||||
thing-type.config.icalendar.eventfilter.datetimeStart.description = Startzeitpunkt relativ zu "jetzt" (inklusiv)
|
||||
thing-type.config.icalendar.eventfilter.datetimeEnd.label = Ende
|
||||
thing-type.config.icalendar.eventfilter.datetimeEnd.description = Endzeitpunkt relativ zu "jetzt" (exklusiv)
|
||||
thing-type.config.icalendar.eventfilter.datetimeRound.label = Abrundung auf Zeiteinheit
|
||||
thing-type.config.icalendar.eventfilter.datetimeRound.description = Zeitpunkt sollen auf die Zeiteinheit abgerundet werden (z.B. auf Mitternacht bei Einheit "Tag")
|
||||
thing-type.config.icalendar.eventfilter.textEventField.label = Event-Feld
|
||||
thing-type.config.icalendar.eventfilter.textEventField.description = Das Feld innerhalb der Ereignis, in dem gefiltert werden soll
|
||||
thing-type.config.icalendar.eventfilter.textEventField.option.SUMMARY = Betreff/Titel
|
||||
thing-type.config.icalendar.eventfilter.textEventField.option.DESCRIPTION = Bescheibung/Inhalt
|
||||
thing-type.config.icalendar.eventfilter.textEventField.option.COMMENT = Kommentar
|
||||
thing-type.config.icalendar.eventfilter.textEventField.option.CONTACT = Kontakt
|
||||
thing-type.config.icalendar.eventfilter.textEventField.option.LOCATION = Ort
|
||||
thing-type.config.icalendar.eventfilter.textEventValue.label = Suchausdruck
|
||||
thing-type.config.icalendar.eventfilter.textValueType.label = Typ des Suchausdrucks
|
||||
thing-type.config.icalendar.eventfilter.textValueType.description = "Text" prüft, ob der Ausdruck enthalten ist, "Regulärer Ausdruck" prüft, ob der Ausdruck aus den Feldwert im Ganzen zutrifft
|
||||
thing-type.config.icalendar.eventfilter.textValueType.option.TEXT = Text
|
||||
thing-type.config.icalendar.eventfilter.textValueType.option.REGEX = Regulärer Ausdruck
|
||||
|
||||
|
||||
# channel types
|
||||
channel-type.icalendar.event_current_title.label = Titel des aktuellen Eintrags
|
||||
@ -35,3 +66,11 @@ channel-type.icalendar.event_next_start.label = Start des n
|
||||
channel-type.icalendar.event_next_start.description = Start des nächsten Eintrags
|
||||
channel-type.icalendar.event_next_end.label = Ende des nächsten Eintrags
|
||||
channel-type.icalendar.event_next_end.description = Ende des nächsten Eintrags
|
||||
channel-group-type.icalendar.result.label = Ergebnis
|
||||
channel-group-type.icalendar.result.description = Ergebnis, gefunden durch den Filter
|
||||
channel-type.icalendar.result_start.label = Ergebnisstart
|
||||
channel-type.icalendar.result_start.description = Startzeitpunkt des gefundenen Ergebnis'
|
||||
channel-type.icalendar.result_end.label = Ergebnisende
|
||||
channel-type.icalendar.result_end.description = Endzeitpunkt des gefundenen Ergebnis'
|
||||
channel-type.icalendar.result_title.label = Ergebnistitel
|
||||
channel-type.icalendar.result_title.description = Titel des gefundenen Ergebnis'
|
||||
|
@ -4,7 +4,7 @@
|
||||
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">
|
||||
|
||||
<thing-type id="calendar">
|
||||
<bridge-type id="calendar">
|
||||
<label>Calendar</label>
|
||||
<description>Calendar based on an iCal calendar.</description>
|
||||
|
||||
@ -55,7 +55,7 @@
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
</bridge-type>
|
||||
|
||||
<channel-type id="event_current_title">
|
||||
<item-type>String</item-type>
|
||||
@ -99,4 +99,113 @@
|
||||
<description>End of the next event in calendar</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="result_start">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>Start of Result</label>
|
||||
<description>Start of the found result in calendar</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="result_end">
|
||||
<item-type>DateTime</item-type>
|
||||
<label>End of Result</label>
|
||||
<description>End of the found result in calendar</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-type id="result_title">
|
||||
<item-type>String</item-type>
|
||||
<label>Title of Result</label>
|
||||
<description>Title of the found result in calendar</description>
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
<channel-group-type id="result">
|
||||
<label>Result Event</label>
|
||||
<description>A resulting event found by filter</description>
|
||||
<channels>
|
||||
<channel typeId="result_start" id="begin"/>
|
||||
<channel typeId="result_end" id="end"/>
|
||||
<channel typeId="result_title" id="title"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
|
||||
<thing-type id="eventfilter">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="calendar"/>
|
||||
</supported-bridge-type-refs>
|
||||
<label>Event Filter</label>
|
||||
<description>Filtered Events from the calendar</description>
|
||||
|
||||
<config-description>
|
||||
<parameter-group name="general">
|
||||
<label>General Filter Options</label>
|
||||
</parameter-group>
|
||||
<parameter-group name="datetime_based">
|
||||
<label>Date and Time based Filter</label>
|
||||
</parameter-group>
|
||||
<parameter-group name="text_based">
|
||||
<label>Text based Filter</label>
|
||||
</parameter-group>
|
||||
|
||||
<parameter name="maxEvents" type="integer" min="0" groupName="general">
|
||||
<label>Maximum Matches</label>
|
||||
<required>true</required>
|
||||
</parameter>
|
||||
<parameter name="refreshTime" type="integer" min="1" groupName="general" unit="min">
|
||||
<label>Refresh Time</label>
|
||||
<description>The frequency in minutes the channels get refreshed</description>
|
||||
<required>true</required>
|
||||
<default>15</default>
|
||||
</parameter>
|
||||
<parameter name="datetimeUnit" type="text" groupName="datetime_based">
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<options>
|
||||
<option value="MINUTE">minute</option>
|
||||
<option value="HOUR">hour</option>
|
||||
<option value="DAY">day</option>
|
||||
<option value="WEEK">week</option>
|
||||
</options>
|
||||
<default>HOUR</default>
|
||||
<label>Date or Time Unit for Start and End</label>
|
||||
</parameter>
|
||||
<parameter name="datetimeStart" type="integer" groupName="datetime_based">
|
||||
<label>Start</label>
|
||||
<description>Start date/time amount to find events relative to "now" (inclusive)</description>
|
||||
</parameter>
|
||||
<parameter name="datetimeEnd" type="integer" groupName="datetime_based">
|
||||
<label>End</label>
|
||||
<description>End date/time amount to find events relative to "now" (exclusive)</description>
|
||||
</parameter>
|
||||
<parameter name="datetimeRound" type="boolean" groupName="datetime_based">
|
||||
<label>Round to Date/Time unit</label>
|
||||
<description>Setting this will round start and end date/time to the unit down (e.g. if unit is day: start and end
|
||||
will be rounded to 0:00 day time)</description>
|
||||
</parameter>
|
||||
<parameter name="textEventField" type="text" groupName="text_based">
|
||||
<label>Event Field</label>
|
||||
<description>iCal field to match</description>
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<options>
|
||||
<option value="SUMMARY">summary/subject</option>
|
||||
<option value="DESCRIPTION">description/content</option>
|
||||
<option value="COMMENT">comment</option>
|
||||
<option value="CONTACT">contact</option>
|
||||
<option value="LOCATION">location</option>
|
||||
</options>
|
||||
</parameter>
|
||||
<parameter name="textEventValue" type="text" groupName="text_based">
|
||||
<label>Event Value</label>
|
||||
</parameter>
|
||||
<parameter name="textValueType" type="text" groupName="text_based">
|
||||
<limitToOptions>true</limitToOptions>
|
||||
<options>
|
||||
<option value="REGEX">Regular Expression</option>
|
||||
<option value="TEXT">Text</option>
|
||||
</options>
|
||||
<default>TEXT</default>
|
||||
<label>Value Type</label>
|
||||
<description>"text" checks the value for containment, "regular expression" matches whole value</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
</thing:thing-descriptions>
|
||||
|
@ -36,8 +36,8 @@ import org.openhab.core.types.Command;
|
||||
* Tests for presentable calendar.
|
||||
*
|
||||
* @author Michael Wodniok - Initial contribution.
|
||||
*
|
||||
* @author Andrew Fiddian-Green - Tests for Command Tag code
|
||||
* @author Michael Wodniok - Extended Tests for filtered Events
|
||||
*
|
||||
*/
|
||||
public class BiweeklyPresentableCalendarTest {
|
||||
@ -542,4 +542,47 @@ public class BiweeklyPresentableCalendarTest {
|
||||
assertNotNull(cmd7);
|
||||
assertEquals(QuantityType.class, cmd7.getClass());
|
||||
}
|
||||
|
||||
@SuppressWarnings("null")
|
||||
@Test
|
||||
public void testGetFilteredEventsBetween() {
|
||||
Event[] expectedFilteredEvents1 = new Event[] {
|
||||
new Event("Test Series in UTC", Instant.parse("2019-09-12T09:05:00Z"),
|
||||
Instant.parse("2019-09-12T09:10:00Z"), ""),
|
||||
new Event("Test Event in UTC+2", Instant.parse("2019-09-14T08:00:00Z"),
|
||||
Instant.parse("2019-09-14T09:00:00Z"), "") };
|
||||
List<Event> realFilteredEvents1 = calendar.getFilteredEventsBetween(Instant.parse("2019-09-12T06:00:00Z"),
|
||||
Instant.parse("2019-09-15T06:00:00Z"), null, 3);
|
||||
assertArrayEquals(expectedFilteredEvents1, realFilteredEvents1.toArray(new Event[0]));
|
||||
|
||||
Event[] expectedFilteredEvents2 = new Event[] {
|
||||
new Event("Evt", Instant.parse("2019-11-10T10:00:00Z"), Instant.parse("2019-11-10T11:45:00Z"), ""),
|
||||
new Event("Evt", Instant.parse("2019-11-17T10:00:00Z"), Instant.parse("2019-11-17T11:45:00Z"), ""),
|
||||
new Event("Evt", Instant.parse("2019-12-01T10:00:00Z"), Instant.parse("2019-12-01T11:45:00Z"), "") };
|
||||
List<Event> realFilteredEvents2 = calendar2.getFilteredEventsBetween(Instant.parse("2019-11-08T06:00:00Z"),
|
||||
Instant.parse("2019-12-31T06:00:00Z"), null, 3);
|
||||
assertArrayEquals(expectedFilteredEvents2, realFilteredEvents2.toArray(new Event[] {}));
|
||||
|
||||
Event[] expectedFilteredEvents3 = new Event[] { new Event("Test Event in UTC+2",
|
||||
Instant.parse("2019-09-14T08:00:00Z"), Instant.parse("2019-09-14T09:00:00Z"), "") };
|
||||
List<Event> realFilteredEvents3 = calendar.getFilteredEventsBetween(Instant.parse("2019-09-12T06:00:00Z"),
|
||||
Instant.parse("2019-09-15T06:00:00Z"),
|
||||
new EventTextFilter(EventTextFilter.Field.SUMMARY, "utc+2", EventTextFilter.Type.TEXT), 3);
|
||||
assertArrayEquals(expectedFilteredEvents3, realFilteredEvents3.toArray(new Event[] {}));
|
||||
|
||||
Event[] expectedFilteredEvents4 = new Event[] { new Event("Test Series in UTC",
|
||||
Instant.parse("2019-09-12T09:05:00Z"), Instant.parse("2019-09-12T09:10:00Z"), "") };
|
||||
List<Event> realFilteredEvents4 = calendar.getFilteredEventsBetween(Instant.parse("2019-09-12T06:00:00Z"),
|
||||
Instant.parse("2019-09-15T06:00:00Z"),
|
||||
new EventTextFilter(EventTextFilter.Field.SUMMARY, ".*UTC$", EventTextFilter.Type.REGEX), 3);
|
||||
assertArrayEquals(expectedFilteredEvents4, realFilteredEvents4.toArray(new Event[] {}));
|
||||
|
||||
List<Event> realFilteredEvents5 = calendar.getFilteredEventsBetween(Instant.parse("2019-09-15T06:00:00Z"),
|
||||
Instant.parse("2019-09-12T06:00:00Z"), null, 3);
|
||||
assertEquals(0, realFilteredEvents5.size());
|
||||
|
||||
List<Event> realFilteredEvents6 = calendar.getFilteredEventsBetween(Instant.parse("2019-09-15T06:00:00Z"),
|
||||
Instant.parse("2019-12-31T00:00:00Z"), null, 3);
|
||||
assertEquals(0, realFilteredEvents6.size());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user