2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.folderwatcher.internal.handler;
15 import static org.openhab.binding.folderwatcher.internal.FolderWatcherBindingConstants.CHANNEL_NEWFILE;
18 import java.io.IOException;
19 import java.nio.file.FileVisitResult;
20 import java.nio.file.FileVisitor;
21 import java.nio.file.Files;
22 import java.nio.file.Path;
23 import java.nio.file.Paths;
24 import java.nio.file.attribute.BasicFileAttributes;
25 import java.util.ArrayList;
26 import java.util.List;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.folderwatcher.internal.common.WatcherCommon;
33 import org.openhab.binding.folderwatcher.internal.config.LocalFolderWatcherConfiguration;
34 import org.openhab.core.OpenHAB;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.binding.BaseThingHandler;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
46 * The {@link LocalFolderWatcherHandler} is responsible for handling commands, which are
47 * sent to one of the channels.
49 * @author Alexandr Salamatov - Initial contribution
52 public class LocalFolderWatcherHandler extends BaseThingHandler {
53 private final Logger logger = LoggerFactory.getLogger(LocalFolderWatcherHandler.class);
54 private LocalFolderWatcherConfiguration config = new LocalFolderWatcherConfiguration();
55 private File currentLocalListingFile = new File(OpenHAB.getUserDataFolder() + File.separator + "FolderWatcher"
56 + File.separator + thing.getUID().getAsString().replace(':', '_') + ".data");
57 private @Nullable ScheduledFuture<?> executionJob;
58 private List<String> previousLocalListing = new ArrayList<>();
60 public LocalFolderWatcherHandler(Thing thing) {
65 public void handleCommand(ChannelUID channelUID, Command command) {
66 logger.debug("Channel {} triggered with command {}", channelUID.getId(), command);
67 if (command instanceof RefreshType) {
68 refreshFolderInformation();
73 public void initialize() {
74 config = getConfigAs(LocalFolderWatcherConfiguration.class);
75 updateStatus(ThingStatus.UNKNOWN);
77 if (!Files.isDirectory(Paths.get(config.localDir))) {
78 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Local directory is not valid");
82 previousLocalListing = WatcherCommon.initStorage(currentLocalListingFile, config.localDir);
83 } catch (IOException e) {
84 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
85 logger.debug("Can't write file {}: {}", currentLocalListingFile, e.getMessage());
89 if (config.pollIntervalLocal > 0) {
90 updateStatus(ThingStatus.ONLINE);
91 executionJob = scheduler.scheduleWithFixedDelay(this::refreshFolderInformation, config.pollIntervalLocal,
92 config.pollIntervalLocal, TimeUnit.SECONDS);
94 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
95 "Polling interval can't be null or negative");
101 public void dispose() {
102 ScheduledFuture<?> executionJob = this.executionJob;
103 if (executionJob != null) {
104 executionJob.cancel(true);
105 this.executionJob = null;
109 private void refreshFolderInformation() {
110 final String rootDir = config.localDir;
112 List<String> currentLocalListing = new ArrayList<>();
114 Files.walkFileTree(Paths.get(rootDir), new FileVisitor<@Nullable Path>() {
116 public FileVisitResult preVisitDirectory(@Nullable Path dir, @Nullable BasicFileAttributes attrs)
119 if (!dir.equals(Paths.get(rootDir)) && !config.listRecursiveLocal) {
120 return FileVisitResult.SKIP_SUBTREE;
123 return FileVisitResult.CONTINUE;
127 public FileVisitResult visitFile(@Nullable Path file, @Nullable BasicFileAttributes attrs)
130 if (Files.isHidden(file) && !config.listHiddenLocal) {
131 return FileVisitResult.CONTINUE;
133 currentLocalListing.add(file.toAbsolutePath().toString());
135 return FileVisitResult.CONTINUE;
139 public FileVisitResult visitFileFailed(@Nullable Path file, @Nullable IOException exc)
141 return FileVisitResult.CONTINUE;
145 public FileVisitResult postVisitDirectory(@Nullable Path dir, @Nullable IOException exc)
147 return FileVisitResult.CONTINUE;
151 List<String> diffLocalListing = new ArrayList<>(currentLocalListing);
152 diffLocalListing.removeAll(previousLocalListing);
153 diffLocalListing.forEach(file -> triggerChannel(CHANNEL_NEWFILE, file));
155 if (!diffLocalListing.isEmpty()) {
156 WatcherCommon.saveNewListing(diffLocalListing, currentLocalListingFile);
158 previousLocalListing = new ArrayList<>(currentLocalListing);
159 } catch (IOException e) {
160 logger.debug("File manipulation error: {}", e.getMessage());