<commit_before>/**
 * 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.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.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.icalendar.internal.config.ICalendarConfiguration;
import org.openhab.binding.icalendar.internal.handler.PullJob.CalendarUpdateListener;
import org.openhab.binding.icalendar.internal.logic.AbstractPresentableCalendar;
import org.openhab.binding.icalendar.internal.logic.CalendarException;
import org.openhab.binding.icalendar.internal.logic.CommandTag;
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.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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The {@link ICalendarHandler} is responsible for handling commands, which are
 * sent to one of the channels.
 *
 * @author <NAME> - Initial contribution
 * @author <NAME> - Support for Command Tags embedded in the Event description
 */
@NonNullByDefault
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(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
    public void dispose() {
        final ScheduledFuture<?> currentUpdateJobFuture = updateJobFuture;
        if (currentUpdateJobFuture != null) {
            currentUpdateJobFuture.cancel(true);
        }
        final ScheduledFuture<?> currentPullJobFuture = pullJobFuture;
        if (currentPullJobFuture != null) {
            currentPullJobFuture.cancel(true);
        }
    }

    @Override
    public void handleCommand(ChannelUID channelUID, Command command) {
        switch (channelUID.getId()) {
            case CHANNEL_CURRENT_EVENT_PRESENT:
            case CHANNEL_CURRENT_EVENT_TITLE:
            case CHANNEL_CURRENT_EVENT_START:
            case CHANNEL_CURRENT_EVENT_END:
            case CHANNEL_NEXT_EVENT_TITLE:
            case CHANNEL_NEXT_EVENT_START:
            case CHANNEL_NEXT_EVENT_END:
                if (command instanceof RefreshType) {
                    updateStates();
                }
                break;
            default:
                logger.warn("Framework sent command to unknown channel with id '{}'", channelUID.getId());
        }
    }

    @Override
    public void initialize() {
        updateStatus(ThingStatus.UNKNOWN);

        final ICalendarConfiguration currentConfiguration = getConfigAs(ICalendarConfiguration.class);
        configuration = currentConfiguration;

        try {
            if ((currentConfiguration.username == null && currentConfiguration.password != null)
                    || (currentConfiguration.username != null && currentConfiguration.password == null)) {
                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, maxSize * 1048576, this);
            } catch (URISyntaxException e) {
                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);
                    updateStates();
                    rescheduleCalendarStateUpdate();
                } else {
                    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, 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.");<commit_msg>[icalendar] Reload calendar file asynchronously (#9227)<commit_after>/**
 * 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.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.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.client.HttpClient;
import org.openhab.binding.icalendar.internal.config.ICalendarConfiguration;
import org.openhab.binding.icalendar.internal.handler.PullJob.CalendarUpdateListener;
import org.openhab.binding.icalendar.internal.logic.AbstractPresentableCalendar;
import org.openhab.binding.icalendar.internal.logic.CalendarException;
import org.openhab.binding.icalendar.internal.logic.CommandTag;
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.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;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * The {@link ICalendarHandler} is responsible for handling commands, which are
 * sent to one of the channels.
 *
 * @author <NAME> - Initial contribution
 * @author <NAME> - Support for Command Tags embedded in the Event description
 */
@NonNullByDefault
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(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
    public void dispose() {
        final ScheduledFuture<?> currentUpdateJobFuture = updateJobFuture;
        if (currentUpdateJobFuture != null) {
            currentUpdateJobFuture.cancel(true);
        }
        final ScheduledFuture<?> currentPullJobFuture = pullJobFuture;
        if (currentPullJobFuture != null) {
            currentPullJobFuture.cancel(true);
        }
    }

    @Override
    public void handleCommand(ChannelUID channelUID, Command command) {
        switch (channelUID.getId()) {
            case CHANNEL_CURRENT_EVENT_PRESENT:
            case CHANNEL_CURRENT_EVENT_TITLE:
            case CHANNEL_CURRENT_EVENT_START:
            case CHANNEL_CURRENT_EVENT_END:
            case CHANNEL_NEXT_EVENT_TITLE:
            case CHANNEL_NEXT_EVENT_START:
            case CHANNEL_NEXT_EVENT_END:
                if (command instanceof RefreshType) {
                    updateStates();
                }
                break;
            default:
                logger.warn("Framework sent command to unknown channel with id '{}'", channelUID.getId());
        }
    }

    @Override
    public void initialize() {
        final ICalendarConfiguration currentConfiguration = getConfigAs(ICalendarConfiguration.class);
        configuration = currentConfiguration;

        try {
            if ((currentConfiguration.username == null && currentConfiguration.password != null)
                    || (currentConfiguration.username != null && currentConfiguration.password == null)) {
                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, maxSize * 1048576, this);
            } catch (URISyntaxException e) {
                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()) {
                updateStatus(ThingStatus.ONLINE);

                scheduler.submit(() -> {
                    // reload calendar file asynchronously
                    if (reloadCalendar()) {
                        updateStates();
                    } else {
                        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, 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.");
{'fraction_non_alphanumeric': 0.0849365266326397, 'fraction_numerical': 0.004057060594163068, 'mean_word_length': 2.894750254841998, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 18, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7666526', 'n_tokens_mistral': 4005, 'n_tokens_neox': 3794, 'n_words': 953}