/**
* Copyright (c) 2010-2016 by the respective copyright holders.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.openhab.binding.caldav_command.internal;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.openhab.binding.caldav_command.CalDavBindingProvider;
import org.openhab.core.binding.AbstractBinding;
import org.openhab.core.binding.BindingProvider;
import org.openhab.core.items.Item;
import org.openhab.core.items.ItemNotFoundException;
import org.openhab.core.items.ItemRegistry;
import org.openhab.core.library.types.DateTimeType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.types.Command;
import org.openhab.io.caldav.CalDavEvent;
import org.openhab.io.caldav.CalDavLoader;
import org.openhab.io.caldav.CalDavQuery;
import org.openhab.io.caldav.CalDavQuery.Sort;
import org.openhab.io.caldav.EventNotifier;
import org.openhab.io.caldav.EventUtils;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of the caldav command binding.
* Every event which is loaded from the server can be triggered with 4 notifications.
* <br/>
* All events which are loaded must fulfill a name syntax for the description.
* All other fields of a event are free to choose.
*
* <pre>
* Sample configuration for event description:
* BEGIN:Livingroom_Heater:23
* END:Livingroom_Heater:16
* => Meaning: when the event starts the heater turns on to 23 and when the event ends
* it turns back to 16
*
* <br/>
* Sample configuration for event description:
* BEGIN:Kitchen_Music:ON
* END:Kitchen_Music:OFF
* => Meaning: when the event starts the music turns on and when the event ends
* it turns off
* </pre>
*
* @see org.openhab.io.caldav.EventNotifier
* @author Robert Delbrück
* @since 1.8.0
*/
public class CalDavBinding extends AbstractBinding<CalDavBindingProvider>implements ManagedService, EventNotifier {
private static final DateTimeFormatter FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd'T'HH:mm:ss");
private static final String KEY_READ_CALENDARS = "readCalendars";
// Use this item as default item if summary does not match pattern: BEGIN:<item>:<value>
private static final String KEY_DEFAULT_ITEM_ON_BEGIN = "defaultItemOnBegin";
private static final Logger logger = LoggerFactory.getLogger(CalDavBinding.class);
private ItemRegistry itemRegistry;
private CalDavLoader calDavLoader;
private List<String> readCalendars = new ArrayList<String>();
private List<String> disabledItems = new ArrayList<String>();
private String defaultItemOnBegin;
private boolean calendarReloaded;
public CalDavBinding() {
}
public void setItemRegistry(ItemRegistry itemRegistry) {
this.itemRegistry = itemRegistry;
}
public void unsetItemRegistry(ItemRegistry itemRegistry) {
this.itemRegistry = null;
}
public void setCalDavLoader(CalDavLoader calDavLoader) {
this.calDavLoader = calDavLoader;
this.calDavLoader.addListener(this);
}
public void unsetCalDavLoader(CalDavLoader calDavLoader) {
this.calDavLoader.removeListener(this);
this.calDavLoader = null;
}
@Override
public void activate() {
}
@Override
public void deactivate() {
}
protected void addBindingProvider(CalDavBindingProvider bindingProvider) {
super.addBindingProvider(bindingProvider);
}
protected void removeBindingProvider(CalDavBindingProvider bindingProvider) {
super.removeBindingProvider(bindingProvider);
}
@Override
public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
if (properties != null) {
logger.debug("reading configuration data...");
String read = (String) properties.get(KEY_READ_CALENDARS);
this.readCalendars.clear();
if (read != null) {
for (String value : read.split(",")) {
this.readCalendars.add(value.trim());
}
}
read = (String) properties.get(KEY_DEFAULT_ITEM_ON_BEGIN);
this.defaultItemOnBegin = read == null ? null : read.trim();
this.reloadCurrentLoadedEvents();
}
}
@Override
public void allBindingsChanged(BindingProvider provider) {
for (String itemName : provider.getItemNames()) {
this.bindingChanged(provider, itemName);
}
}
@Override
public void bindingChanged(BindingProvider provider, String itemName) {
if (provider.getItemNames().contains(itemName)) {
final CalDavNextEventConfig config = ((CalDavBindingProvider) provider).getConfig(itemName);
List<CalDavEvent> events = calDavLoader.getEvents(new CalDavQuery(this.readCalendars, DateTime.now()));
this.updateItemState(config, events);
}
}
private void reloadCurrentLoadedEvents() {
if (this.calDavLoader == null) {
return;
}
for (String calendarKey : this.readCalendars) {
calendarReloaded(calendarKey);
}
}
private CalDavBindingProvider getCalDavBindingProvider() {
for (CalDavBindingProvider provider : providers) {
return provider;
}
return null;
}
private synchronized void handleForEventPreview() {
CalDavBindingProvider provider = getCalDavBindingProvider();
if (provider == null) {
logger.error("cannot find any provider");
return;
}
List<CalDavEvent> events = calDavLoader.getEvents(new CalDavQuery(this.readCalendars, DateTime.now()));
for (String configItemName : provider.getItemNames()) {
final CalDavNextEventConfig config = provider.getConfig(configItemName);
this.updateItemState(config, events);
}
}
@Override
protected void internalReceiveCommand(String itemName, Command command) {
if (!(command instanceof OnOffType)) {
logger.trace("invalid command for DISABLE (just SwitchItems allowed)");
return;
}
// get binding provider
CalDavBindingProvider provider = getCalDavBindingProvider();
if (provider == null) {
logger.error("cannot find any provider");
return;
}
CalDavNextEventConfig config = provider.getConfig(itemName);
if (config == null) {
logger.error("no config found for item {}", itemName);
return;
}
if (config.getType() != CalDavType.DISABLE) {
logger.trace("can just use commands for type=DISABLE");
return;
}
if (command == OnOffType.ON) {
logger.info("execution for '{}' disabled", config.getItemNameToListenTo());
this.disabledItems.add(config.getItemNameToListenTo());
} else if (command == OnOffType.OFF) {
logger.info("execution for '{}' enabled", config.getItemNameToListenTo());
this.disabledItems.remove(config.getItemNameToListenTo());
}
}
@Override
public void eventRemoved(CalDavEvent event) {
}
@Override
public void eventLoaded(CalDavEvent event) {
}
@Override
public void eventBegins(CalDavEvent event) {
if (!readCalendars.contains(event.getCalendarId())) {
return;
}
if (this.itemRegistry == null) {
logger.error("item registry is not set");
return;
}
this.doAction(event, EventUtils.SCOPE_BEGIN);
this.handleForEventPreview();
}
@Override
public void eventEnds(CalDavEvent event) {
if (!readCalendars.contains(event.getCalendarId())) {
return;
}
if (this.itemRegistry == null) {
logger.error("item registry is not set");
return;
}
this.doAction(event, EventUtils.SCOPE_END);
this.handleForEventPreview();
}
@Override
public void calendarReloaded(String calendarId) {
if (!readCalendars.contains(calendarId)) {
return;
}
if (this.itemRegistry == null) {
logger.error("item registry is not set");
return;
}
if (!this.calendarReloaded) {
this.doActionInitial();
}
this.handleForEventPreview();
this.calendarReloaded = true;
}
private void doActionInitial() {
List<CalDavEvent> events = calDavLoader
.getEvents(new CalDavQuery(this.readCalendars, DateTime.now(), Sort.ASCENDING));
Map<String, EventUtils.EventContent> map = new HashMap<String, EventUtils.EventContent>();
for (CalDavEvent calDavEvent : events) {
final List<EventUtils.EventContent> parseContent = EventUtils.parseContent(calDavEvent, this.itemRegistry,
null);
for (EventUtils.EventContent eventContent : parseContent) {
if (disabledItems.contains(eventContent.getItem().getName())) {
// changing this is item is disabled, do not change it
continue;
}
EventUtils.EventContent currentEventContent = map.get(eventContent.getItem().getName());
if (eventContent.getTime().isBefore(DateTime.now()) && (currentEventContent == null
|| eventContent.getTime().isAfter(currentEventContent.getTime()))) {
map.put(eventContent.getItem().getName(), eventContent);
}
}
}
for (EventUtils.EventContent currentEventContent : map.values()) {
eventPublisher.sendCommand(currentEventContent.getItem().getName(), currentEventContent.getCommand());
logger.debug("setting initial value for {} to {}", currentEventContent.getItem().getName(),
currentEventContent.getCommand());
}
}
private void doAction(CalDavEvent event, String scope) {
final List<EventUtils.EventContent> parseContent = EventUtils.parseContent(event, this.itemRegistry, scope,
defaultItemOnBegin);
outer: for (EventUtils.EventContent eventContent : parseContent) {
logger.trace("checking for disabled for item '{}' and groups '{}'", eventContent.getItem().getName(),
eventContent.getItem().getGroupNames());
for (String groupName : eventContent.getItem().getGroupNames()) {
if (disabledItems.contains(groupName)) {
continue outer;
}
}
if (disabledItems.contains(eventContent.getItem().getName())) {
continue outer;
}
logger.info("sending command '{}' to item '{}' from event '{}'", eventContent.getCommand(),
eventContent.getItem().getName(), event.getShortName());
eventPublisher.sendCommand(eventContent.getItem().getName(), eventContent.getCommand());
}
}
private void updateItemState(CalDavNextEventConfig config, List<CalDavEvent> events) {
String itemName = config.getItemNameToListenTo();
String itemNamePreview = config.getItemName();
logger.trace("update item state for item: {}", itemName);
Command state = null;
DateTime time = null;
if (calDavLoader == null) {
logger.warn("caldav loader is not set");
return;
}
for (CalDavEvent calDavEvent : events) {
try {
final Item item = this.itemRegistry.getItem(itemName);
final List<EventUtils.EventContent> parseContent = EventUtils.parseContent(calDavEvent, item);
for (EventUtils.EventContent eventContent : parseContent) {
if (!eventContent.getTime().isBefore(DateTime.now())
&& (time == null || time.isAfter(eventContent.getTime()))) {
time = eventContent.getTime();
state = eventContent.getCommand();
}
}
} catch (ItemNotFoundException e) {
logger.error("item {} could not be found", itemName);
}
}
if (time == null && config.getType() != CalDavType.DISABLE) {
// no item found
eventPublisher.postUpdate(itemNamePreview, org.openhab.core.types.UnDefType.UNDEF);
return;
}
CalDavType type = config.getType();
logger.trace("handling event of type: {}", type);
if (type == CalDavType.VALUE) {
logger.debug("setting value for '{}' to: {}", itemNamePreview, state);
eventPublisher.sendCommand(itemNamePreview, state);
} else if (type == CalDavType.DATE) {
Command c = new DateTimeType(FORMATTER.print(time));
logger.debug("setting value for '{}' to: {}", itemNamePreview, c);
eventPublisher.sendCommand(itemNamePreview, c);
} else if (type == CalDavType.DISABLE) {
// nothing to do
return;
} else {
logger.warn("unhandled type: {}", type);
}
}
}