mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 07:02:02 +01:00
[folderwatcher] Initial contribution (#10045)
Signed-off-by: Alexandr Salamatov <wpgnetworks@gmail.com>
This commit is contained in:
parent
95259b1095
commit
5a0a325344
@ -77,6 +77,7 @@
|
||||
/bundles/org.openhab.binding.feed/ @svilenvul
|
||||
/bundles/org.openhab.binding.feican/ @Hilbrand
|
||||
/bundles/org.openhab.binding.fmiweather/ @ssalonen
|
||||
/bundles/org.openhab.binding.folderwatcher/ @goopilot
|
||||
/bundles/org.openhab.binding.folding/ @fa2k
|
||||
/bundles/org.openhab.binding.foobot/ @airboxlab @Hilbrand
|
||||
/bundles/org.openhab.binding.freebox/ @lolodomo
|
||||
|
@ -371,6 +371,11 @@
|
||||
<artifactId>org.openhab.binding.fmiweather</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.folderwatcher</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.folding</artifactId>
|
||||
|
13
bundles/org.openhab.binding.folderwatcher/NOTICE
Normal file
13
bundles/org.openhab.binding.folderwatcher/NOTICE
Normal file
@ -0,0 +1,13 @@
|
||||
This content is produced and maintained by the openHAB project.
|
||||
|
||||
* Project home: https://www.openhab.org
|
||||
|
||||
== Declared Project Licenses
|
||||
|
||||
This program and the accompanying materials are made available under the terms
|
||||
of the Eclipse Public License 2.0 which is available at
|
||||
https://www.eclipse.org/legal/epl-2.0/.
|
||||
|
||||
== Source Code
|
||||
|
||||
https://github.com/openhab/openhab-addons
|
82
bundles/org.openhab.binding.folderwatcher/README.md
Executable file
82
bundles/org.openhab.binding.folderwatcher/README.md
Executable file
@ -0,0 +1,82 @@
|
||||
# FolderWatcher Binding
|
||||
|
||||
This binding is intended to monitor FTP and local folder and its subfolders and notify of new files
|
||||
|
||||
## Supported Things
|
||||
|
||||
Currently the binding support two types of things: `ftpfolder` and `localfolder`.
|
||||
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
The `ftpfolder` thing has the following configuration options:
|
||||
|
||||
| Parameter | Name | Description | Required | Default value |
|
||||
|-------------|--------------|------------------------------------------------------------------------------------------------------------------------|----------|---------------|
|
||||
| ftpAddress | FTP server | IP address of FTP server | yes | n/a |
|
||||
| ftpPort | FTP port | Port of FTP server | yes | 21 |
|
||||
| secureMode | FTP Security | FTP Security | yes | None |
|
||||
| ftpUsername | Username | FTP user name | yes | n/a |
|
||||
| ftpPassword | Password | FTP password | yes | n/a |
|
||||
| ftpDir | RootDir | Root directory to be watched | yes | n/a |
|
||||
| listRecursiveFtp | List Sub Folders | Allow listing of sub folders | yes | No |
|
||||
| listHidden | List Hidden | Allow listing of hidden files | yes | false |
|
||||
| connectionTimeout | Connection timeout, s | Connection timeout for FTP request | yes | 30 |
|
||||
| pollInterval | Polling interval, s | Interval for polling folder changes | yes | 60 |
|
||||
| diffHours | Time stamp difference, h | How many hours back to analyze | yes | 24 |
|
||||
|
||||
The `localfolder` thing has the following configuration options:
|
||||
|
||||
| Parameter | Name | Description | Required | Default value |
|
||||
|-------------|--------------|------------------------------------------------------------------------------------------------------------------------|----------|---------------|
|
||||
| localDir | Local Directory | Local directory to be watched | yes | n/a |
|
||||
| listHiddenLocal | List Hidden | Allow listing of hidden files | yes | No |
|
||||
| pollIntervalLocal | Polling interval, s | Interval for polling folder changes | yes | 60 |
|
||||
| listRecursiveLocal | List Sub Folders | Allow listing of sub folders | yes | No |
|
||||
|
||||
## Events
|
||||
|
||||
This binding currently supports the following events:
|
||||
|
||||
| Channel Type ID | Item Type | Description |
|
||||
|-----------------|--------------|----------------------------------------------------------------------------------------|
|
||||
| newftpfile | String | A new file name discovered on FTP |
|
||||
| newlocalfile | String | A new file name discovered on in local folder |
|
||||
|
||||
|
||||
## Full Example
|
||||
|
||||
Thing configuration:
|
||||
|
||||
```java
|
||||
folderwatcher:localfolder:myLocalFolder [ localDir="/tmp/dumps", pollIntervalLocal=60, listHiddenLocal="false", listRecursiveLocal="false" ]
|
||||
folderwatcher:ftpfolder:myLocalFolder [ ftpAddress="192.168.0.222", ftpPort=21, secureMode="EXPLICIT", ftpUsername="ftpuser", ftpPassword="ftppass",ftpDir="/suvcams/192.168.0.209",listHidden="true",listRecursiveFtp="true",connectionTimeout=33,pollInterval=66,diffHours=25]
|
||||
```
|
||||
|
||||
### Using in a rule:
|
||||
|
||||
FTP example:
|
||||
|
||||
```java
|
||||
rule "New FTP file"
|
||||
when
|
||||
Channel 'folderwatcher:ftpfolder:XXXXX:newfile' triggered
|
||||
then
|
||||
|
||||
logInfo('NewFTPFile', receivedEvent.toString())
|
||||
|
||||
end
|
||||
```
|
||||
|
||||
Local folder example:
|
||||
|
||||
```java
|
||||
rule "New Local file"
|
||||
when
|
||||
Channel 'folderwatcher:localfolder:XXXXX:newfile' triggered
|
||||
then
|
||||
|
||||
logInfo('NewLocalFile', receivedEvent.toString())
|
||||
|
||||
end
|
||||
```
|
25
bundles/org.openhab.binding.folderwatcher/pom.xml
Normal file
25
bundles/org.openhab.binding.folderwatcher/pom.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||
<version>3.1.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.folderwatcher</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: FolderWatcher Binding</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>commons-net</groupId>
|
||||
<artifactId>commons-net</artifactId>
|
||||
<version>3.7.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -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>
|
@ -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";
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
@ -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>
|
@ -108,6 +108,7 @@
|
||||
<module>org.openhab.binding.feed</module>
|
||||
<module>org.openhab.binding.feican</module>
|
||||
<module>org.openhab.binding.fmiweather</module>
|
||||
<module>org.openhab.binding.folderwatcher</module>
|
||||
<module>org.openhab.binding.folding</module>
|
||||
<module>org.openhab.binding.foobot</module>
|
||||
<module>org.openhab.binding.freebox</module>
|
||||
|
Loading…
Reference in New Issue
Block a user