[folderwatcher] Initial contribution (#10045)

Signed-off-by: Alexandr Salamatov <wpgnetworks@gmail.com>
This commit is contained in:
goopilot
2021-02-10 12:45:47 -06:00
committed by GitHub
parent 95259b1095
commit 5a0a325344
17 changed files with 925 additions and 0 deletions

View File

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

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2021 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.folderwatcher.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link FolderWatcherBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Alexandr Salamatov - Initial contribution
*/
@NonNullByDefault
public class FolderWatcherBindingConstants {
private static final String BINDING_ID = "folderwatcher";
public static final ThingTypeUID THING_TYPE_FTPFOLDER = new ThingTypeUID(BINDING_ID, "ftpfolder");
public static final ThingTypeUID THING_TYPE_LOCALFOLDER = new ThingTypeUID(BINDING_ID, "localfolder");
public static final String CHANNEL_NEWFILE = "newfile";
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright (c) 2010-2021 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.folderwatcher.internal;
import static org.openhab.binding.folderwatcher.internal.FolderWatcherBindingConstants.*;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.folderwatcher.internal.handler.FtpFolderWatcherHandler;
import org.openhab.binding.folderwatcher.internal.handler.LocalFolderWatcherHandler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
/**
* The {@link FolderWatcherHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Alexandr Salamatov - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.folderwatcher", service = ThingHandlerFactory.class)
public class FolderWatcherHandlerFactory extends BaseThingHandlerFactory {
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_FTPFOLDER,
THING_TYPE_LOCALFOLDER);
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_FTPFOLDER.equals(thingTypeUID)) {
return new FtpFolderWatcherHandler(thing);
} else if (THING_TYPE_LOCALFOLDER.equals(thingTypeUID)) {
return new LocalFolderWatcherHandler(thing);
}
return null;
}
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2021 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.folderwatcher.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link FolderWatcherBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Alexandr Salamatov - Initial contribution
*/
@NonNullByDefault
public enum SecureMode {
NONE,
IMPLICIT,
EXPLICIT
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2021 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.folderwatcher.internal.common;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.util.List;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link WatcherCommon} class contains commonly used methods.
*
* @author Alexandr Salamatov - Initial contribution
*/
@NonNullByDefault
public class WatcherCommon {
private static void initFile(File file, String watchDir) throws IOException {
try (BufferedWriter fileWriter = new BufferedWriter(new FileWriter(file))) {
fileWriter.write(watchDir);
fileWriter.newLine();
}
}
public static List<String> initStorage(File file, String watchDir) throws IOException {
List<String> returnList = List.of();
List<String> currentFileListing = List.of();
if (!file.exists()) {
Files.createDirectories(file.toPath().getParent());
initFile(file, watchDir);
} else {
currentFileListing = Files.readAllLines(file.toPath().toAbsolutePath());
if (currentFileListing.get(0).equals(watchDir)) {
returnList = currentFileListing;
} else {
initFile(file, watchDir);
}
}
return returnList;
}
public static void saveNewListing(List<String> newList, File listingFile) throws IOException {
try (BufferedWriter fileWriter = new BufferedWriter(new FileWriter(listingFile, true))) {
for (String newFile : newList) {
fileWriter.write(newFile);
fileWriter.newLine();
}
}
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2021 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.folderwatcher.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.folderwatcher.internal.SecureMode;
/**
* The {@link FtpFolderWatcherConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Alexandr Salamatov - Initial contribution
*/
@NonNullByDefault
public class FtpFolderWatcherConfiguration {
public String ftpAddress = "";
public int ftpPort;
public String ftpUsername = "";
public String ftpPassword = "";
public String ftpDir = "";
public int pollInterval;
public int connectionTimeout;
public boolean listHidden;
public int diffHours;
public boolean listRecursiveFtp;
public SecureMode secureMode = SecureMode.NONE;
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright (c) 2010-2021 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.folderwatcher.internal.config;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The {@link LocalFolderWatcherConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Alexandr Salamatov - Initial contribution
*/
@NonNullByDefault
public class LocalFolderWatcherConfiguration {
public String localDir = "";
public boolean listHiddenLocal;
public int pollIntervalLocal;
public boolean listRecursiveLocal;
}

View File

@@ -0,0 +1,247 @@
/**
* Copyright (c) 2010-2021 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.folderwatcher.internal.handler;
import static org.openhab.binding.folderwatcher.internal.FolderWatcherBindingConstants.CHANNEL_NEWFILE;
import java.io.File;
import java.io.IOException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.net.ftp.FTPSClient;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.folderwatcher.internal.common.WatcherCommon;
import org.openhab.binding.folderwatcher.internal.config.FtpFolderWatcherConfiguration;
import org.openhab.core.OpenHAB;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link FtpFolderWatcherHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Alexandr Salamatov - Initial contribution
*/
@NonNullByDefault
public class FtpFolderWatcherHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(FtpFolderWatcherHandler.class);
private FtpFolderWatcherConfiguration config = new FtpFolderWatcherConfiguration();
private @Nullable File currentFtpListingFile;
private @Nullable ScheduledFuture<?> executionJob, initJob;
private FTPClient ftp = new FTPClient();
private List<String> previousFtpListing = new ArrayList<>();
public FtpFolderWatcherHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Channel {} triggered with command {}", channelUID.getId(), command);
if (command instanceof RefreshType) {
refreshFTPFolderInformation();
}
}
@Override
public void initialize() {
File currentFtpListingFile;
config = getConfigAs(FtpFolderWatcherConfiguration.class);
updateStatus(ThingStatus.UNKNOWN);
if (config.connectionTimeout <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Connection timeout can't be negative");
return;
}
if (config.ftpPort < 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "FTP port can't be negative");
return;
}
if (config.pollInterval <= 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Polling interval can't be null or negative");
}
currentFtpListingFile = new File(OpenHAB.getUserDataFolder() + File.separator + "FolderWatcher" + File.separator
+ thing.getUID().getAsString().replace(':', '_') + ".data");
try {
this.currentFtpListingFile = currentFtpListingFile;
previousFtpListing = WatcherCommon.initStorage(currentFtpListingFile, config.ftpAddress + config.ftpDir);
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
logger.debug("Can't write file {}, error message {}", currentFtpListingFile, e.getMessage());
return;
}
this.initJob = scheduler.scheduleWithFixedDelay(this::connectionKeepAlive, 0, config.pollInterval,
TimeUnit.SECONDS);
}
@Override
public void dispose() {
ScheduledFuture<?> executionJob = this.executionJob;
ScheduledFuture<?> initJob = this.initJob;
if (executionJob != null) {
executionJob.cancel(true);
}
if (initJob != null) {
initJob.cancel(true);
}
if (ftp.isConnected()) {
try {
ftp.logout();
ftp.disconnect();
} catch (IOException e) {
logger.debug("Error terminating FTP connection: ", e);
}
}
}
private void listDirectory(FTPClient ftpClient, String dirPath, boolean recursive, List<String> dirFiles)
throws IOException {
Instant dateNow = Instant.now();
for (FTPFile file : ftpClient.listFiles(dirPath)) {
String currentFileName = file.getName();
if (currentFileName.equals(".") || currentFileName.equals("..")) {
continue;
}
String filePath = dirPath + "/" + currentFileName;
if (file.isDirectory()) {
if (recursive) {
try {
listDirectory(ftpClient, filePath, recursive, dirFiles);
} catch (IOException e) {
logger.debug("Can't read FTP directory: {}", filePath, e);
}
}
} else {
long diff = ChronoUnit.HOURS.between(file.getTimestamp().toInstant(), dateNow);
if (diff < config.diffHours) {
dirFiles.add("ftp:/" + ftpClient.getRemoteAddress() + filePath);
}
}
}
}
private void connectionKeepAlive() {
if (!ftp.isConnected()) {
switch (config.secureMode) {
case NONE:
ftp = new FTPClient();
break;
case IMPLICIT:
ftp = new FTPSClient(true);
break;
case EXPLICIT:
ftp = new FTPSClient(false);
break;
}
int reply = 0;
ftp.setListHiddenFiles(config.listHidden);
ftp.setConnectTimeout(config.connectionTimeout * 1000);
try {
ftp.connect(config.ftpAddress, config.ftpPort);
reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"FTP server refused connection.");
return;
}
} catch (IOException e) {
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException e2) {
logger.debug("Error disconneting, lost connection? : {}", e2.getMessage());
}
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
return;
}
try {
if (!ftp.login(config.ftpUsername, config.ftpPassword)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ftp.getReplyString());
ftp.logout();
return;
}
updateStatus(ThingStatus.ONLINE);
ScheduledFuture<?> executionJob = this.executionJob;
if (executionJob != null) {
executionJob.cancel(true);
}
this.executionJob = scheduler.scheduleWithFixedDelay(this::refreshFTPFolderInformation, 0,
config.pollInterval, TimeUnit.SECONDS);
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
private void refreshFTPFolderInformation() {
String ftpRootDir = config.ftpDir;
final File currentFtpListingFile = this.currentFtpListingFile;
if (ftp.isConnected()) {
ftp.enterLocalPassiveMode();
try {
if (ftpRootDir.endsWith("/")) {
ftpRootDir = ftpRootDir.substring(0, ftpRootDir.length() - 1);
}
if (!ftpRootDir.startsWith("/")) {
ftpRootDir = "/" + ftpRootDir;
}
List<String> currentFtpListing = new ArrayList<>();
listDirectory(ftp, ftpRootDir, config.listRecursiveFtp, currentFtpListing);
List<String> diffFtpListing = new ArrayList<>(currentFtpListing);
diffFtpListing.removeAll(previousFtpListing);
diffFtpListing.forEach(file -> triggerChannel(CHANNEL_NEWFILE, file));
if (!diffFtpListing.isEmpty() && currentFtpListingFile != null) {
try {
WatcherCommon.saveNewListing(diffFtpListing, currentFtpListingFile);
} catch (IOException e2) {
logger.debug("Can't save new listing into file: {}", e2.getMessage());
}
}
previousFtpListing = new ArrayList<>(currentFtpListing);
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"FTP connection lost. " + e.getMessage());
try {
ftp.disconnect();
} catch (IOException e1) {
logger.debug("Error disconneting, lost connection? {}", e1.getMessage());
}
}
} else {
logger.debug("FTP connection lost.");
}
}
}

View File

@@ -0,0 +1,162 @@
/**
* Copyright (c) 2010-2021 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.folderwatcher.internal.handler;
import static org.openhab.binding.folderwatcher.internal.FolderWatcherBindingConstants.CHANNEL_NEWFILE;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
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.openhab.binding.folderwatcher.internal.common.WatcherCommon;
import org.openhab.binding.folderwatcher.internal.config.LocalFolderWatcherConfiguration;
import org.openhab.core.OpenHAB;
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.types.Command;
import org.openhab.core.types.RefreshType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link LocalFolderWatcherHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Alexandr Salamatov - Initial contribution
*/
@NonNullByDefault
public class LocalFolderWatcherHandler extends BaseThingHandler {
private final Logger logger = LoggerFactory.getLogger(LocalFolderWatcherHandler.class);
private LocalFolderWatcherConfiguration config = new LocalFolderWatcherConfiguration();
private File currentLocalListingFile = new File(OpenHAB.getUserDataFolder() + File.separator + "FolderWatcher"
+ File.separator + thing.getUID().getAsString().replace(':', '_') + ".data");
private @Nullable ScheduledFuture<?> executionJob;
private List<String> previousLocalListing = new ArrayList<>();
public LocalFolderWatcherHandler(Thing thing) {
super(thing);
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.debug("Channel {} triggered with command {}", channelUID.getId(), command);
if (command instanceof RefreshType) {
refreshFolderInformation();
}
}
@Override
public void initialize() {
config = getConfigAs(LocalFolderWatcherConfiguration.class);
updateStatus(ThingStatus.UNKNOWN);
if (!Files.isDirectory(Paths.get(config.localDir))) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Local directory is not valid");
return;
}
try {
previousLocalListing = WatcherCommon.initStorage(currentLocalListingFile, config.localDir);
} catch (IOException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
logger.debug("Can't write file {}: {}", currentLocalListingFile, e.getMessage());
return;
}
if (config.pollIntervalLocal > 0) {
updateStatus(ThingStatus.ONLINE);
executionJob = scheduler.scheduleWithFixedDelay(this::refreshFolderInformation, config.pollIntervalLocal,
config.pollIntervalLocal, TimeUnit.SECONDS);
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Polling interval can't be null or negative");
return;
}
}
@Override
public void dispose() {
ScheduledFuture<?> executionJob = this.executionJob;
if (executionJob != null) {
executionJob.cancel(true);
}
}
private void refreshFolderInformation() {
final String rootDir = config.localDir;
try {
List<String> currentLocalListing = new ArrayList<>();
Files.walkFileTree(Paths.get(rootDir), new FileVisitor<@Nullable Path>() {
@Override
public FileVisitResult preVisitDirectory(@Nullable Path dir, @Nullable BasicFileAttributes attrs)
throws IOException {
if (dir != null) {
if (!dir.equals(Paths.get(rootDir)) && !config.listRecursiveLocal) {
return FileVisitResult.SKIP_SUBTREE;
}
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFile(@Nullable Path file, @Nullable BasicFileAttributes attrs)
throws IOException {
if (file != null) {
if (Files.isHidden(file) && !config.listHiddenLocal) {
return FileVisitResult.CONTINUE;
}
currentLocalListing.add(file.toAbsolutePath().toString());
}
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult visitFileFailed(@Nullable Path file, @Nullable IOException exc)
throws IOException {
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(@Nullable Path dir, @Nullable IOException exc)
throws IOException {
return FileVisitResult.CONTINUE;
}
});
List<String> diffLocalListing = new ArrayList<>(currentLocalListing);
diffLocalListing.removeAll(previousLocalListing);
diffLocalListing.forEach(file -> triggerChannel(CHANNEL_NEWFILE, file));
if (!diffLocalListing.isEmpty()) {
WatcherCommon.saveNewListing(diffLocalListing, currentLocalListingFile);
}
previousLocalListing = new ArrayList<>(currentLocalListing);
} catch (IOException e) {
logger.debug("File manipulation error: {}", e.getMessage());
}
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="folderwatcher" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>FolderWatcher Binding</name>
<description>This binding will monitor specified location for new files and trigger event channel with new file names.</description>
</binding:binding>

View File

@@ -0,0 +1,126 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="folderwatcher"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="ftpfolder">
<label>FTP Folder</label>
<description>FTP folder to be watched</description>
<channels>
<channel id="newfile" typeId="newfile-channel"/>
</channels>
<config-description>
<parameter name="ftpAddress" type="text" required="true">
<label>FTP Server</label>
<description>Address of FTP server</description>
<context>network-address</context>
</parameter>
<parameter name="ftpPort" type="integer" min="1" max="65535">
<label>FTP Port</label>
<default>21</default>
<description>FTP server's port</description>
</parameter>
<parameter name="secureMode" type="text">
<label>FTP Security</label>
<limitToOptions>true</limitToOptions>
<options>
<option value="NONE">None</option>
<option value="IMPLICIT">TLS/SSL Implicit</option>
<option value="EXPLICIT">TLS/SSL Explicit</option>
</options>
<default>NONE</default>
<description>FTP Security settings</description>
<advanced>true</advanced>
</parameter>
<parameter name="ftpUsername" type="text" required="true">
<label>Username</label>
<description>User name</description>
</parameter>
<parameter name="ftpPassword" type="text" required="true">
<label>Password</label>
<description>FTP server password</description>
<context>password</context>
</parameter>
<parameter name="ftpDir" type="text" required="true">
<label>Root Directory</label>
<description>Root directory to be watched</description>
</parameter>
<parameter name="listHidden" type="boolean">
<label>List Hidden</label>
<default>false</default>
<description>Allow listing of hidden files</description>
<advanced>true</advanced>
</parameter>
<parameter name="listRecursiveFtp" type="boolean">
<label>List Sub Folders</label>
<default>false</default>
<description>Allow listing of sub folders</description>
<advanced>true</advanced>
</parameter>
<parameter name="connectionTimeout" type="integer" min="1" unit="s">
<label>Connection Timeout</label>
<description>Connection timeout for FTP request, sec</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="pollInterval" type="integer" min="1" unit="s">
<label>Polling Interval</label>
<description>Interval for polling folder changes, sec</description>
<default>60</default>
<advanced>true</advanced>
</parameter>
<parameter name="diffHours" type="integer" min="1" unit="h">
<label>Timestamp Difference</label>
<description>How many hours back to analyze</description>
<default>24</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<channel-type id="newfile-channel">
<kind>trigger</kind>
<label>New File Name(s)</label>
<description>A new file name</description>
<category>String</category>
<event/>
</channel-type>
<thing-type id="localfolder">
<label>Local Folder</label>
<description>Local folder to be watched</description>
<channels>
<channel id="newfile" typeId="newfile-channel"/>
</channels>
<config-description>
<parameter name="localDir" type="text" required="true">
<label>Local Directory</label>
<description>Local directory to be watched</description>
</parameter>
<parameter name="pollIntervalLocal" type="integer" min="1" unit="s">
<label>Polling Interval</label>
<description>Interval for polling folder changes, sec</description>
<default>60</default>
<advanced>true</advanced>
</parameter>
<parameter name="listHiddenLocal" type="boolean">
<label>List Hidden</label>
<default>false</default>
<description>Allow listing of hidden files</description>
<advanced>true</advanced>
</parameter>
<parameter name="listRecursiveLocal" type="boolean">
<label>List Sub Folders</label>
<default>false</default>
<description>Allow listing of sub folders</description>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
</thing:thing-descriptions>