mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 23:22:02 +01:00
[Folderwatcher] AWS S3 buckets monitoring support (#14669)
* Add S3 Thing Signed-off-by: Alexandr Salamatov <goopilot@gmail.com>
This commit is contained in:
parent
2c006ccd31
commit
3b2c574684
@ -1,17 +1,17 @@
|
|||||||
# FolderWatcher Binding
|
# FolderWatcher Binding
|
||||||
|
|
||||||
This binding is intended to monitor FTP and local folder and its subfolders and notify of new files
|
This binding is intended to monitor FTP, local folder and S3 bucket and its subfolders and notify of new files
|
||||||
|
|
||||||
## Supported Things
|
## Supported Things
|
||||||
|
|
||||||
Currently the binding support two types of things: `ftpfolder` and `localfolder`.
|
Currently the binding support three types of things: `ftpfolder`, `localfolder` and `s3bucket`.
|
||||||
|
|
||||||
## Thing Configuration
|
## Thing Configuration
|
||||||
|
|
||||||
The `ftpfolder` thing has the following configuration options:
|
The `ftpfolder` thing has the following configuration options:
|
||||||
|
|
||||||
| Parameter | Name | Description | Required | Default value |
|
| Parameter | Name | Description | Required | Default value |
|
||||||
|-------------|--------------|------------------------------------------------------------------------------------------------------------------------|----------|---------------|
|
|-------------------|--------------------------|-------------------------------------|----------|---------------|
|
||||||
| ftpAddress | FTP server | IP address of FTP server | yes | n/a |
|
| ftpAddress | FTP server | IP address of FTP server | yes | n/a |
|
||||||
| ftpPort | FTP port | Port of FTP server | yes | 21 |
|
| ftpPort | FTP port | Port of FTP server | yes | 21 |
|
||||||
| secureMode | FTP Security | FTP Security | yes | None |
|
| secureMode | FTP Security | FTP Security | yes | None |
|
||||||
@ -27,20 +27,30 @@ The `ftpfolder` thing has the following configuration options:
|
|||||||
The `localfolder` thing has the following configuration options:
|
The `localfolder` thing has the following configuration options:
|
||||||
|
|
||||||
| Parameter | Name | Description | Required | Default value |
|
| Parameter | Name | Description | Required | Default value |
|
||||||
|-------------|--------------|------------------------------------------------------------------------------------------------------------------------|----------|---------------|
|
|--------------------|---------------------|-------------------------------------|----------|---------------|
|
||||||
| localDir | Local Directory | Local directory to be watched | yes | n/a |
|
| localDir | Local Directory | Local directory to be watched | yes | n/a |
|
||||||
| listHiddenLocal | List Hidden | Allow listing of hidden files | yes | No |
|
| listHiddenLocal | List Hidden | Allow listing of hidden files | yes | No |
|
||||||
| pollIntervalLocal | Polling interval, s | Interval for polling folder changes | yes | 60 |
|
| pollIntervalLocal | Polling interval, s | Interval for polling folder changes | yes | 60 |
|
||||||
| listRecursiveLocal | List Sub Folders | Allow listing of sub folders | yes | No |
|
| listRecursiveLocal | List Sub Folders | Allow listing of sub folders | yes | No |
|
||||||
|
|
||||||
|
The `s3bucket` thing has the following configuration options:
|
||||||
|
|
||||||
|
| Parameter | Name | Description | Required | Default value |
|
||||||
|
|----------------|----------------------|----------------------------------------------------|----------|---------------|
|
||||||
|
| s3BucketName | S3 Bucket Name | Name of the S3 bucket to be watched | yes | n/a |
|
||||||
|
| s3Path | S3 Path | S3 path (folder) to be monitored | no | n/a |
|
||||||
|
| pollIntervalS3 | Polling Interval | Interval for polling S3 bucket changes, in seconds | yes | 60 |
|
||||||
|
| awsKey | AWS Access Key | AWS access key | no | n/a |
|
||||||
|
| awsSecret | AWS Secret | AWS secret | no | n/a |
|
||||||
|
| awsRegion | AWS Region | AWS region of S3 bucket | yes | "" |
|
||||||
|
| s3Anonymous | Anonymous Connection | Connect anonymously (works for public buckets) | yes | true |
|
||||||
## Events
|
## Events
|
||||||
|
|
||||||
This binding currently supports the following events:
|
This binding currently supports the following events:
|
||||||
|
|
||||||
| Channel Type ID | Item Type | Description |
|
| Channel Type ID | Item Type | Description |
|
||||||
|-----------------|--------------|----------------------------------------------------------------------------------------|
|
|-----------------|-----------|----------------------------|
|
||||||
| newftpfile | String | A new file name discovered on FTP |
|
| newfile | String | A new file name discovered |
|
||||||
| newlocalfile | String | A new file name discovered on in local folder |
|
|
||||||
|
|
||||||
## Full Example
|
## Full Example
|
||||||
|
|
||||||
@ -48,7 +58,9 @@ Thing configuration:
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
folderwatcher:localfolder:myLocalFolder [ localDir="/myfolder", pollIntervalLocal=60, listHiddenLocal="false", listRecursiveLocal="false" ]
|
folderwatcher:localfolder:myLocalFolder [ localDir="/myfolder", pollIntervalLocal=60, listHiddenLocal="false", listRecursiveLocal="false" ]
|
||||||
folderwatcher:ftpfolder:myLocalFolder [ ftpAddress="X.X.X.X", ftpPort=21, secureMode="EXPLICIT", ftpUsername="username", ftpPassword="password",ftpDir="/myfolder/",listHidden="true",listRecursiveFtp="true",connectionTimeout=33,pollInterval=66,diffHours=25]
|
folderwatcher:ftpfolder:myLocalFolder [ ftpAddress="X.X.X.X", ftpPort=21, secureMode="EXPLICIT", ftpUsername="username", ftpPassword="password", ftpDir="/myfolder/", listHidden="true", listRecursiveFtp="true", connectionTimeout=33, pollInterval=66, diffHours=25 ]
|
||||||
|
folderwatcher:s3bucket:myS3bucket [ s3BucketName="mypublic-bucket", pollIntervalS3=60, awsRegion="us-west-1", s3Anonymous="true" ]
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Using in a rule:
|
### Using in a rule:
|
||||||
@ -58,10 +70,10 @@ FTP example:
|
|||||||
```java
|
```java
|
||||||
rule "New FTP file"
|
rule "New FTP file"
|
||||||
when
|
when
|
||||||
Channel 'folderwatcher:ftpfolder:XXXXX:newfile' triggered
|
Channel "folderwatcher:ftpfolder:myLocalFolder:newfile" triggered
|
||||||
then
|
then
|
||||||
|
|
||||||
logInfo('NewFTPFile', receivedEvent.toString())
|
logInfo("NewFTPFile", receivedEvent.toString())
|
||||||
|
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
@ -71,10 +83,23 @@ Local folder example:
|
|||||||
```java
|
```java
|
||||||
rule "New Local file"
|
rule "New Local file"
|
||||||
when
|
when
|
||||||
Channel 'folderwatcher:localfolder:XXXXX:newfile' triggered
|
Channel "folderwatcher:localfolder:myFTPFolder:newfile" triggered
|
||||||
then
|
then
|
||||||
|
|
||||||
logInfo('NewLocalFile', receivedEvent.toString())
|
logInfo("NewLocalFile", receivedEvent.toString())
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
S3 bucket example:
|
||||||
|
|
||||||
|
```java
|
||||||
|
rule "New S3 file"
|
||||||
|
when
|
||||||
|
Channel "folderwatcher:s3bucket:myS3bucket:newfile" triggered
|
||||||
|
then
|
||||||
|
|
||||||
|
logInfo("NewS3File", receivedEvent.toString())
|
||||||
|
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
@ -26,5 +26,6 @@ public class FolderWatcherBindingConstants {
|
|||||||
private static final String BINDING_ID = "folderwatcher";
|
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_FTPFOLDER = new ThingTypeUID(BINDING_ID, "ftpfolder");
|
||||||
public static final ThingTypeUID THING_TYPE_LOCALFOLDER = new ThingTypeUID(BINDING_ID, "localfolder");
|
public static final ThingTypeUID THING_TYPE_LOCALFOLDER = new ThingTypeUID(BINDING_ID, "localfolder");
|
||||||
|
public static final ThingTypeUID THING_TYPE_S3BUCKET = new ThingTypeUID(BINDING_ID, "s3bucket");
|
||||||
public static final String CHANNEL_NEWFILE = "newfile";
|
public static final String CHANNEL_NEWFILE = "newfile";
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,16 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.folderwatcher.internal.handler.FtpFolderWatcherHandler;
|
import org.openhab.binding.folderwatcher.internal.handler.FtpFolderWatcherHandler;
|
||||||
import org.openhab.binding.folderwatcher.internal.handler.LocalFolderWatcherHandler;
|
import org.openhab.binding.folderwatcher.internal.handler.LocalFolderWatcherHandler;
|
||||||
|
import org.openhab.binding.folderwatcher.internal.handler.S3BucketWatcherHandler;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
import org.openhab.core.thing.Thing;
|
import org.openhab.core.thing.Thing;
|
||||||
import org.openhab.core.thing.ThingTypeUID;
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||||
import org.openhab.core.thing.binding.ThingHandler;
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||||
|
import org.osgi.service.component.annotations.Activate;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.osgi.service.component.annotations.Reference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link FolderWatcherHandlerFactory} is responsible for creating things and thing
|
* The {@link FolderWatcherHandlerFactory} is responsible for creating things and thing
|
||||||
@ -38,7 +42,13 @@ import org.osgi.service.component.annotations.Component;
|
|||||||
public class FolderWatcherHandlerFactory extends BaseThingHandlerFactory {
|
public class FolderWatcherHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
|
||||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_FTPFOLDER,
|
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_FTPFOLDER,
|
||||||
THING_TYPE_LOCALFOLDER);
|
THING_TYPE_LOCALFOLDER, THING_TYPE_S3BUCKET);
|
||||||
|
private HttpClientFactory httpClientFactory;
|
||||||
|
|
||||||
|
@Activate
|
||||||
|
public FolderWatcherHandlerFactory(final @Reference HttpClientFactory httpClientFactory) {
|
||||||
|
this.httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||||
@ -53,6 +63,8 @@ public class FolderWatcherHandlerFactory extends BaseThingHandlerFactory {
|
|||||||
return new FtpFolderWatcherHandler(thing);
|
return new FtpFolderWatcherHandler(thing);
|
||||||
} else if (THING_TYPE_LOCALFOLDER.equals(thingTypeUID)) {
|
} else if (THING_TYPE_LOCALFOLDER.equals(thingTypeUID)) {
|
||||||
return new LocalFolderWatcherHandler(thing);
|
return new LocalFolderWatcherHandler(thing);
|
||||||
|
} else if (THING_TYPE_S3BUCKET.equals(thingTypeUID)) {
|
||||||
|
return new S3BucketWatcherHandler(thing, httpClientFactory);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.api;
|
||||||
|
|
||||||
|
import static org.eclipse.jetty.http.HttpHeader.*;
|
||||||
|
import static org.eclipse.jetty.http.HttpMethod.*;
|
||||||
|
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import javax.xml.parsers.DocumentBuilder;
|
||||||
|
import javax.xml.parsers.DocumentBuilderFactory;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jetty.client.HttpClient;
|
||||||
|
import org.eclipse.jetty.client.api.ContentResponse;
|
||||||
|
import org.eclipse.jetty.client.api.Request;
|
||||||
|
import org.openhab.binding.folderwatcher.internal.api.auth.AWS4SignerBase;
|
||||||
|
import org.openhab.binding.folderwatcher.internal.api.auth.AWS4SignerForAuthorizationHeader;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
import org.xml.sax.InputSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link S3Actions} class contains AWS S3 API implementation.
|
||||||
|
*
|
||||||
|
* @author Alexandr Salamatov - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class S3Actions {
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
private static final Duration REQUEST_TIMEOUT = Duration.ofMinutes(1);
|
||||||
|
private static final String CONTENT_TYPE = "application/xml";
|
||||||
|
private URL bucketUri;
|
||||||
|
private String region;
|
||||||
|
private String awsAccessKey;
|
||||||
|
private String awsSecretKey;
|
||||||
|
|
||||||
|
public S3Actions(HttpClientFactory httpClientFactory, String bucketName, String region) {
|
||||||
|
this(httpClientFactory, bucketName, region, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public S3Actions(HttpClientFactory httpClientFactory, String bucketName, String region, String awsAccessKey,
|
||||||
|
String awsSecretKey) {
|
||||||
|
this.httpClient = httpClientFactory.getCommonHttpClient();
|
||||||
|
try {
|
||||||
|
this.bucketUri = new URL("http://" + bucketName + ".s3." + region + ".amazonaws.com");
|
||||||
|
} catch (MalformedURLException e) {
|
||||||
|
throw new RuntimeException("Unable to parse service endpoint: " + e.getMessage());
|
||||||
|
}
|
||||||
|
this.region = region;
|
||||||
|
this.awsAccessKey = awsAccessKey;
|
||||||
|
this.awsSecretKey = awsSecretKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> listBucket(String prefix) throws Exception {
|
||||||
|
Map<String, String> headers = new HashMap<String, String>();
|
||||||
|
Map<String, String> params = new HashMap<String, String>();
|
||||||
|
return listObjectsV2(prefix, headers, params);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> listObjectsV2(String prefix, Map<String, String> headers, Map<String, String> params)
|
||||||
|
throws Exception {
|
||||||
|
params.put("list-type", "2");
|
||||||
|
params.put("prefix", prefix);
|
||||||
|
if (!awsAccessKey.isEmpty() || !awsSecretKey.isEmpty()) {
|
||||||
|
headers.put("x-amz-content-sha256", AWS4SignerBase.EMPTY_BODY_SHA256);
|
||||||
|
AWS4SignerForAuthorizationHeader signer = new AWS4SignerForAuthorizationHeader(this.bucketUri, "GET", "s3",
|
||||||
|
region);
|
||||||
|
String authorization = signer.computeSignature(headers, params, AWS4SignerBase.EMPTY_BODY_SHA256,
|
||||||
|
awsAccessKey, awsSecretKey);
|
||||||
|
headers.put("Authorization", authorization);
|
||||||
|
}
|
||||||
|
|
||||||
|
headers.put(ACCEPT.toString(), CONTENT_TYPE);
|
||||||
|
Request request = httpClient.newRequest(this.bucketUri.toString()) //
|
||||||
|
.method(GET) //
|
||||||
|
.timeout(REQUEST_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS); //
|
||||||
|
|
||||||
|
for (String headerKey : headers.keySet()) {
|
||||||
|
request.header(headerKey, headers.get(headerKey));
|
||||||
|
}
|
||||||
|
for (String paramKey : params.keySet()) {
|
||||||
|
request.param(paramKey, params.get(paramKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentResponse contentResponse = request.send();
|
||||||
|
if (contentResponse.getStatus() != 200) {
|
||||||
|
throw new Exception("HTTP Response is not 200");
|
||||||
|
}
|
||||||
|
|
||||||
|
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||||
|
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
|
||||||
|
InputSource is = new InputSource(new StringReader(contentResponse.getContentAsString()));
|
||||||
|
Document doc = docBuilder.parse(is);
|
||||||
|
NodeList nameNodesList = doc.getElementsByTagName("Key");
|
||||||
|
List<String> returnList = new ArrayList<>();
|
||||||
|
|
||||||
|
if (nameNodesList.getLength() == 0) {
|
||||||
|
throw new Exception("No files deceted in the bucket");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < nameNodesList.getLength(); i++) {
|
||||||
|
returnList.add(nameNodesList.item(i).getFirstChild().getTextContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
nameNodesList = doc.getElementsByTagName("IsTruncated");
|
||||||
|
if (nameNodesList.getLength() > 0) {
|
||||||
|
if (nameNodesList.item(0).getFirstChild().getTextContent().equals("true")) {
|
||||||
|
nameNodesList = doc.getElementsByTagName("NextContinuationToken");
|
||||||
|
if (nameNodesList.getLength() > 0) {
|
||||||
|
String continueToken = nameNodesList.item(0).getFirstChild().getTextContent();
|
||||||
|
params.clear();
|
||||||
|
headers.clear();
|
||||||
|
params.put("continuation-token", continueToken);
|
||||||
|
returnList.addAll(listObjectsV2(prefix, headers, params));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return returnList;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.api.auth;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.SimpleTimeZone;
|
||||||
|
import java.util.SortedMap;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.folderwatcher.internal.api.util.BinaryUtils;
|
||||||
|
import org.openhab.binding.folderwatcher.internal.api.util.HttpUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link AWS4SignerBase} class contains based methods for AWS S3 API authentication.
|
||||||
|
* <p>
|
||||||
|
* Based on offical AWS example {@see https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-examples-using-sdks.html}
|
||||||
|
*
|
||||||
|
* @author Alexandr Salamatov - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public abstract class AWS4SignerBase {
|
||||||
|
|
||||||
|
public static final String EMPTY_BODY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";
|
||||||
|
public static final String UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
|
||||||
|
public static final String SCHEME = "AWS4";
|
||||||
|
public static final String ALGORITHM = "HMAC-SHA256";
|
||||||
|
public static final String TERMINATOR = "aws4_request";
|
||||||
|
public static final String ISO8601BasicFormat = "yyyyMMdd'T'HHmmss'Z'";
|
||||||
|
public static final String DateStringFormat = "yyyyMMdd";
|
||||||
|
protected URL endpointUrl;
|
||||||
|
protected String httpMethod;
|
||||||
|
protected String serviceName;
|
||||||
|
protected String regionName;
|
||||||
|
protected final SimpleDateFormat dateTimeFormat;
|
||||||
|
protected final SimpleDateFormat dateStampFormat;
|
||||||
|
|
||||||
|
public AWS4SignerBase(URL endpointUrl, String httpMethod, String serviceName, String regionName) {
|
||||||
|
this.endpointUrl = endpointUrl;
|
||||||
|
this.httpMethod = httpMethod;
|
||||||
|
this.serviceName = serviceName;
|
||||||
|
this.regionName = regionName;
|
||||||
|
|
||||||
|
dateTimeFormat = new SimpleDateFormat(ISO8601BasicFormat);
|
||||||
|
dateTimeFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
|
||||||
|
dateStampFormat = new SimpleDateFormat(DateStringFormat);
|
||||||
|
dateStampFormat.setTimeZone(new SimpleTimeZone(0, "UTC"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getCanonicalizeHeaderNames(Map<String, String> headers) {
|
||||||
|
List<String> sortedHeaders = new ArrayList<String>();
|
||||||
|
sortedHeaders.addAll(headers.keySet());
|
||||||
|
Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
for (String header : sortedHeaders) {
|
||||||
|
if (buffer.length() > 0) {
|
||||||
|
buffer.append(";");
|
||||||
|
}
|
||||||
|
buffer.append(header.toLowerCase());
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getCanonicalizedHeaderString(Map<String, String> headers) {
|
||||||
|
if (headers == null || headers.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> sortedHeaders = new ArrayList<String>();
|
||||||
|
sortedHeaders.addAll(headers.keySet());
|
||||||
|
Collections.sort(sortedHeaders, String.CASE_INSENSITIVE_ORDER);
|
||||||
|
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
|
for (String key : sortedHeaders) {
|
||||||
|
buffer.append(key.toLowerCase().replaceAll("\\s+", " ") + ":" + headers.get(key).replaceAll("\\s+", " "));
|
||||||
|
buffer.append("\n");
|
||||||
|
}
|
||||||
|
return buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getCanonicalRequest(URL endpoint, String httpMethod, String queryParameters,
|
||||||
|
String canonicalizedHeaderNames, String canonicalizedHeaders, String bodyHash) {
|
||||||
|
String canonicalRequest = httpMethod + "\n" + getCanonicalizedResourcePath(endpoint) + "\n" + queryParameters
|
||||||
|
+ "\n" + canonicalizedHeaders + "\n" + canonicalizedHeaderNames + "\n" + bodyHash;
|
||||||
|
return canonicalRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getCanonicalizedResourcePath(URL endpoint) {
|
||||||
|
if (endpoint == null) {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
String path = endpoint.getPath();
|
||||||
|
if (path == null || path.isEmpty()) {
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
String encodedPath = HttpUtils.urlEncode(path, true);
|
||||||
|
if (encodedPath.startsWith("/")) {
|
||||||
|
return encodedPath;
|
||||||
|
} else {
|
||||||
|
return "/".concat(encodedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getCanonicalizedQueryString(Map<String, String> parameters) {
|
||||||
|
if (parameters == null || parameters.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
SortedMap<String, String> sorted = new TreeMap<String, String>();
|
||||||
|
Iterator<Map.Entry<String, String>> pairs = parameters.entrySet().iterator();
|
||||||
|
|
||||||
|
while (pairs.hasNext()) {
|
||||||
|
Map.Entry<String, String> pair = pairs.next();
|
||||||
|
String key = pair.getKey();
|
||||||
|
String value = pair.getValue();
|
||||||
|
sorted.put(HttpUtils.urlEncode(key, false), HttpUtils.urlEncode(value, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder builder = new StringBuilder();
|
||||||
|
pairs = sorted.entrySet().iterator();
|
||||||
|
while (pairs.hasNext()) {
|
||||||
|
Map.Entry<String, String> pair = pairs.next();
|
||||||
|
builder.append(pair.getKey());
|
||||||
|
builder.append("=");
|
||||||
|
builder.append(pair.getValue());
|
||||||
|
if (pairs.hasNext()) {
|
||||||
|
builder.append("&");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String getStringToSign(String scheme, String algorithm, String dateTime, String scope,
|
||||||
|
String canonicalRequest) {
|
||||||
|
String stringToSign = scheme + "-" + algorithm + "\n" + dateTime + "\n" + scope + "\n"
|
||||||
|
+ BinaryUtils.toHex(hash(canonicalRequest));
|
||||||
|
return stringToSign;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] hash(String text) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
md.update(text.getBytes("UTF-8"));
|
||||||
|
return md.digest();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] hash(byte[] data) {
|
||||||
|
try {
|
||||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||||
|
md.update(data);
|
||||||
|
return md.digest();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unable to compute hash while signing request: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static byte[] sign(String stringData, byte[] key, String algorithm) {
|
||||||
|
try {
|
||||||
|
byte[] data = stringData.getBytes("UTF-8");
|
||||||
|
Mac mac = Mac.getInstance(algorithm);
|
||||||
|
mac.init(new SecretKeySpec(key, algorithm));
|
||||||
|
return mac.doFinal(data);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Unable to calculate a request signature: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.api.auth;
|
||||||
|
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.binding.folderwatcher.internal.api.util.BinaryUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link AWS4SignerForAuthorizationHeader} class contains methods for AWS S3 API authentication using HTTP(S)
|
||||||
|
* headers.
|
||||||
|
* <p>
|
||||||
|
* Based on offical AWS example {@see https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-examples-using-sdks.html}
|
||||||
|
*
|
||||||
|
* @author Alexandr Salamatov - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class AWS4SignerForAuthorizationHeader extends AWS4SignerBase {
|
||||||
|
|
||||||
|
public AWS4SignerForAuthorizationHeader(URL endpointUrl, String httpMethod, String serviceName, String regionName) {
|
||||||
|
super(endpointUrl, httpMethod, serviceName, regionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String computeSignature(Map<String, String> headers, Map<String, String> queryParameters, String bodyHash,
|
||||||
|
String awsAccessKey, String awsSecretKey) {
|
||||||
|
Date now = new Date();
|
||||||
|
String dateTimeStamp = dateTimeFormat.format(now);
|
||||||
|
headers.put("x-amz-date", dateTimeStamp);
|
||||||
|
String hostHeader = endpointUrl.getHost();
|
||||||
|
int port = endpointUrl.getPort();
|
||||||
|
if (port > -1) {
|
||||||
|
hostHeader.concat(":" + Integer.toString(port));
|
||||||
|
}
|
||||||
|
headers.put("Host", hostHeader);
|
||||||
|
|
||||||
|
String canonicalizedHeaderNames = getCanonicalizeHeaderNames(headers);
|
||||||
|
String canonicalizedHeaders = getCanonicalizedHeaderString(headers);
|
||||||
|
String canonicalizedQueryParameters = getCanonicalizedQueryString(queryParameters);
|
||||||
|
String canonicalRequest = getCanonicalRequest(endpointUrl, httpMethod, canonicalizedQueryParameters,
|
||||||
|
canonicalizedHeaderNames, canonicalizedHeaders, bodyHash);
|
||||||
|
String dateStamp = dateStampFormat.format(now);
|
||||||
|
String scope = dateStamp + "/" + regionName + "/" + serviceName + "/" + TERMINATOR;
|
||||||
|
String stringToSign = getStringToSign(SCHEME, ALGORITHM, dateTimeStamp, scope, canonicalRequest);
|
||||||
|
byte[] kSecret = (SCHEME + awsSecretKey).getBytes();
|
||||||
|
byte[] kDate = sign(dateStamp, kSecret, "HmacSHA256");
|
||||||
|
byte[] kRegion = sign(regionName, kDate, "HmacSHA256");
|
||||||
|
byte[] kService = sign(serviceName, kRegion, "HmacSHA256");
|
||||||
|
byte[] kSigning = sign(TERMINATOR, kService, "HmacSHA256");
|
||||||
|
byte[] signature = sign(stringToSign, kSigning, "HmacSHA256");
|
||||||
|
String credentialsAuthorizationHeader = "Credential=" + awsAccessKey + "/" + scope;
|
||||||
|
String signedHeadersAuthorizationHeader = "SignedHeaders=" + canonicalizedHeaderNames;
|
||||||
|
String signatureAuthorizationHeader = "Signature=" + BinaryUtils.toHex(signature);
|
||||||
|
String authorizationHeader = SCHEME + "-" + ALGORITHM + " " + credentialsAuthorizationHeader + ", "
|
||||||
|
+ signedHeadersAuthorizationHeader + ", " + signatureAuthorizationHeader;
|
||||||
|
return authorizationHeader;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.api.util;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link BinaryUtils} class contains methods for binary interactions.
|
||||||
|
* <p>
|
||||||
|
* Based on offical AWS example {@see https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-examples-using-sdks.html}
|
||||||
|
*
|
||||||
|
* @author Alexandr Salamatov - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class BinaryUtils {
|
||||||
|
public static String toHex(byte[] data) {
|
||||||
|
StringBuilder sb = new StringBuilder(data.length * 2);
|
||||||
|
for (int i = 0; i < data.length; i++) {
|
||||||
|
String hex = Integer.toHexString(data[i]);
|
||||||
|
if (hex.length() == 1) {
|
||||||
|
sb.append("0");
|
||||||
|
} else if (hex.length() == 8) {
|
||||||
|
hex = hex.substring(6);
|
||||||
|
}
|
||||||
|
sb.append(hex);
|
||||||
|
}
|
||||||
|
return sb.toString().toLowerCase(Locale.getDefault());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] fromHex(String hexData) {
|
||||||
|
byte[] result = new byte[(hexData.length() + 1) / 2];
|
||||||
|
String hexNumber = null;
|
||||||
|
int stringOffset = 0;
|
||||||
|
int byteOffset = 0;
|
||||||
|
while (stringOffset < hexData.length()) {
|
||||||
|
hexNumber = hexData.substring(stringOffset, stringOffset + 2);
|
||||||
|
stringOffset += 2;
|
||||||
|
result[byteOffset++] = (byte) Integer.parseInt(hexNumber, 16);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.api.util;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link HttpUtils} class contains metohdos related to HTTP(S).
|
||||||
|
* <p>
|
||||||
|
* Based on offical AWS example {@see https://docs.aws.amazon.com/AmazonS3/latest/API/sig-v4-examples-using-sdks.html}
|
||||||
|
*
|
||||||
|
* @author Alexandr Salamatov - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class HttpUtils {
|
||||||
|
public static String urlEncode(String url, boolean keepPathSlash) {
|
||||||
|
String encoded;
|
||||||
|
try {
|
||||||
|
encoded = URLEncoder.encode(url, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException("UTF-8 encoding is not supported.", e);
|
||||||
|
}
|
||||||
|
if (keepPathSlash) {
|
||||||
|
encoded = encoded.replace("%2F", "/");
|
||||||
|
}
|
||||||
|
return encoded;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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 S3BucketWatcherConfiguration {
|
||||||
|
public String s3BucketName = "";
|
||||||
|
public String s3Path = "";
|
||||||
|
public boolean s3Anonymous;
|
||||||
|
public int pollIntervalS3;
|
||||||
|
public String awsKey = "";
|
||||||
|
public String awsSecret = "";
|
||||||
|
public String awsRegion = "";
|
||||||
|
}
|
@ -108,9 +108,11 @@ public class FtpFolderWatcherHandler extends BaseThingHandler {
|
|||||||
ScheduledFuture<?> initJob = this.initJob;
|
ScheduledFuture<?> initJob = this.initJob;
|
||||||
if (executionJob != null) {
|
if (executionJob != null) {
|
||||||
executionJob.cancel(true);
|
executionJob.cancel(true);
|
||||||
|
this.executionJob = null;
|
||||||
}
|
}
|
||||||
if (initJob != null) {
|
if (initJob != null) {
|
||||||
initJob.cancel(true);
|
initJob.cancel(true);
|
||||||
|
this.initJob = null;
|
||||||
}
|
}
|
||||||
if (ftp.isConnected()) {
|
if (ftp.isConnected()) {
|
||||||
try {
|
try {
|
||||||
|
@ -102,6 +102,7 @@ public class LocalFolderWatcherHandler extends BaseThingHandler {
|
|||||||
ScheduledFuture<?> executionJob = this.executionJob;
|
ScheduledFuture<?> executionJob = this.executionJob;
|
||||||
if (executionJob != null) {
|
if (executionJob != null) {
|
||||||
executionJob.cancel(true);
|
executionJob.cancel(true);
|
||||||
|
this.executionJob = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,131 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2023 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.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.api.S3Actions;
|
||||||
|
import org.openhab.binding.folderwatcher.internal.common.WatcherCommon;
|
||||||
|
import org.openhab.binding.folderwatcher.internal.config.S3BucketWatcherConfiguration;
|
||||||
|
import org.openhab.core.OpenHAB;
|
||||||
|
import org.openhab.core.io.net.http.HttpClientFactory;
|
||||||
|
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 S3BucketWatcherHandler} is responsible for handling commands, which are
|
||||||
|
* sent to one of the channels.
|
||||||
|
*
|
||||||
|
* @author Alexandr Salamatov - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class S3BucketWatcherHandler extends BaseThingHandler {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(S3BucketWatcherHandler.class);
|
||||||
|
private S3BucketWatcherConfiguration config = new S3BucketWatcherConfiguration();
|
||||||
|
private File currentS3ListingFile = new File(OpenHAB.getUserDataFolder() + File.separator + "FolderWatcher"
|
||||||
|
+ File.separator + thing.getUID().getAsString().replace(':', '_') + ".data");
|
||||||
|
private @Nullable ScheduledFuture<?> executionJob;
|
||||||
|
private List<String> previousS3Listing = new ArrayList<>();
|
||||||
|
private HttpClientFactory httpClientFactory;
|
||||||
|
private @Nullable S3Actions s3;
|
||||||
|
|
||||||
|
public S3BucketWatcherHandler(Thing thing, HttpClientFactory httpClientFactory) {
|
||||||
|
super(thing);
|
||||||
|
this.httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
logger.debug("Channel {} triggered with command {}", channelUID.getId(), command);
|
||||||
|
if (command instanceof RefreshType) {
|
||||||
|
refreshS3BucketInformation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
config = getConfigAs(S3BucketWatcherConfiguration.class);
|
||||||
|
|
||||||
|
if (config.s3Anonymous) {
|
||||||
|
s3 = new S3Actions(httpClientFactory, config.s3BucketName, config.awsRegion);
|
||||||
|
} else {
|
||||||
|
s3 = new S3Actions(httpClientFactory, config.s3BucketName, config.awsRegion, config.awsKey,
|
||||||
|
config.awsSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
previousS3Listing = WatcherCommon.initStorage(currentS3ListingFile, config.s3BucketName);
|
||||||
|
} catch (IOException e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
|
||||||
|
logger.debug("Can't write file {}: {}", currentS3ListingFile, e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refreshS3BucketInformation()) {
|
||||||
|
if (config.pollIntervalS3 > 0) {
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
executionJob = scheduler.scheduleWithFixedDelay(this::refreshS3BucketInformation, config.pollIntervalS3,
|
||||||
|
config.pollIntervalS3, TimeUnit.SECONDS);
|
||||||
|
} else {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
|
"Polling interval must be greater then 0 seconds");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean refreshS3BucketInformation() {
|
||||||
|
List<String> currentS3Listing = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
currentS3Listing = s3.listBucket(config.s3Path);
|
||||||
|
List<String> difS3Listing = new ArrayList<>(currentS3Listing);
|
||||||
|
difS3Listing.removeAll(previousS3Listing);
|
||||||
|
difS3Listing.forEach(file -> triggerChannel(CHANNEL_NEWFILE, file));
|
||||||
|
|
||||||
|
if (!difS3Listing.isEmpty()) {
|
||||||
|
WatcherCommon.saveNewListing(difS3Listing, currentS3ListingFile);
|
||||||
|
}
|
||||||
|
previousS3Listing = new ArrayList<>(currentS3Listing);
|
||||||
|
} catch (Exception e) {
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Can't connect to the bucket");
|
||||||
|
logger.debug("Can't connect to the bucket: {}", e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
ScheduledFuture<?> executionJob = this.executionJob;
|
||||||
|
if (executionJob != null) {
|
||||||
|
executionJob.cancel(true);
|
||||||
|
this.executionJob = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,11 +9,13 @@ thing-type.folderwatcher.ftpfolder.label = FTP Folder
|
|||||||
thing-type.folderwatcher.ftpfolder.description = FTP folder to be watched
|
thing-type.folderwatcher.ftpfolder.description = FTP folder to be watched
|
||||||
thing-type.folderwatcher.localfolder.label = Local Folder
|
thing-type.folderwatcher.localfolder.label = Local Folder
|
||||||
thing-type.folderwatcher.localfolder.description = Local folder to be watched
|
thing-type.folderwatcher.localfolder.description = Local folder to be watched
|
||||||
|
thing-type.folderwatcher.s3bucket.label = AWS S3 Bucket
|
||||||
|
thing-type.folderwatcher.s3bucket.description = AWS S3 bucket to be watched
|
||||||
|
|
||||||
# thing types config
|
# thing types config
|
||||||
|
|
||||||
thing-type.config.folderwatcher.ftpfolder.connectionTimeout.label = Connection Timeout
|
thing-type.config.folderwatcher.ftpfolder.connectionTimeout.label = Connection Timeout
|
||||||
thing-type.config.folderwatcher.ftpfolder.connectionTimeout.description = Connection timeout for FTP request, sec
|
thing-type.config.folderwatcher.ftpfolder.connectionTimeout.description = Connection timeout for FTP request, in seconds
|
||||||
thing-type.config.folderwatcher.ftpfolder.diffHours.label = Timestamp Difference
|
thing-type.config.folderwatcher.ftpfolder.diffHours.label = Timestamp Difference
|
||||||
thing-type.config.folderwatcher.ftpfolder.diffHours.description = How many hours back to analyze
|
thing-type.config.folderwatcher.ftpfolder.diffHours.description = How many hours back to analyze
|
||||||
thing-type.config.folderwatcher.ftpfolder.ftpAddress.label = FTP Server
|
thing-type.config.folderwatcher.ftpfolder.ftpAddress.label = FTP Server
|
||||||
@ -31,7 +33,7 @@ thing-type.config.folderwatcher.ftpfolder.listHidden.description = Allow listing
|
|||||||
thing-type.config.folderwatcher.ftpfolder.listRecursiveFtp.label = List Sub Folders
|
thing-type.config.folderwatcher.ftpfolder.listRecursiveFtp.label = List Sub Folders
|
||||||
thing-type.config.folderwatcher.ftpfolder.listRecursiveFtp.description = Allow listing of sub folders
|
thing-type.config.folderwatcher.ftpfolder.listRecursiveFtp.description = Allow listing of sub folders
|
||||||
thing-type.config.folderwatcher.ftpfolder.pollInterval.label = Polling Interval
|
thing-type.config.folderwatcher.ftpfolder.pollInterval.label = Polling Interval
|
||||||
thing-type.config.folderwatcher.ftpfolder.pollInterval.description = Interval for polling folder changes, sec
|
thing-type.config.folderwatcher.ftpfolder.pollInterval.description = Interval for polling folder changes, in seconds
|
||||||
thing-type.config.folderwatcher.ftpfolder.secureMode.label = FTP Security
|
thing-type.config.folderwatcher.ftpfolder.secureMode.label = FTP Security
|
||||||
thing-type.config.folderwatcher.ftpfolder.secureMode.description = FTP Security settings
|
thing-type.config.folderwatcher.ftpfolder.secureMode.description = FTP Security settings
|
||||||
thing-type.config.folderwatcher.ftpfolder.secureMode.option.NONE = None
|
thing-type.config.folderwatcher.ftpfolder.secureMode.option.NONE = None
|
||||||
@ -44,7 +46,21 @@ thing-type.config.folderwatcher.localfolder.listRecursiveLocal.description = All
|
|||||||
thing-type.config.folderwatcher.localfolder.localDir.label = Local Directory
|
thing-type.config.folderwatcher.localfolder.localDir.label = Local Directory
|
||||||
thing-type.config.folderwatcher.localfolder.localDir.description = Local directory to be watched
|
thing-type.config.folderwatcher.localfolder.localDir.description = Local directory to be watched
|
||||||
thing-type.config.folderwatcher.localfolder.pollIntervalLocal.label = Polling Interval
|
thing-type.config.folderwatcher.localfolder.pollIntervalLocal.label = Polling Interval
|
||||||
thing-type.config.folderwatcher.localfolder.pollIntervalLocal.description = Interval for polling folder changes, sec
|
thing-type.config.folderwatcher.localfolder.pollIntervalLocal.description = Interval for polling folder changes, in seconds
|
||||||
|
thing-type.config.folderwatcher.s3bucket.awsKey.label = AWS Access Key
|
||||||
|
thing-type.config.folderwatcher.s3bucket.awsKey.description = AWS access key
|
||||||
|
thing-type.config.folderwatcher.s3bucket.awsRegion.label = AWS Region
|
||||||
|
thing-type.config.folderwatcher.s3bucket.awsRegion.description = AWS region of S3 bucket
|
||||||
|
thing-type.config.folderwatcher.s3bucket.awsSecret.label = AWS Secret
|
||||||
|
thing-type.config.folderwatcher.s3bucket.awsSecret.description = AWS secret
|
||||||
|
thing-type.config.folderwatcher.s3bucket.pollIntervalS3.label = Polling Interval
|
||||||
|
thing-type.config.folderwatcher.s3bucket.pollIntervalS3.description = Interval for polling S3 bucket changes, in seconds
|
||||||
|
thing-type.config.folderwatcher.s3bucket.s3Anonymous.label = Anonymous Connection
|
||||||
|
thing-type.config.folderwatcher.s3bucket.s3Anonymous.description = Connect anonymously (works for public buckets)
|
||||||
|
thing-type.config.folderwatcher.s3bucket.s3BucketName.label = S3 Bucket Name
|
||||||
|
thing-type.config.folderwatcher.s3bucket.s3BucketName.description = Name of the S3 bucket to be watched
|
||||||
|
thing-type.config.folderwatcher.s3bucket.s3Path.label = S3 Path
|
||||||
|
thing-type.config.folderwatcher.s3bucket.s3Path.description = S3 path (folder) to be monitored
|
||||||
|
|
||||||
# channel types
|
# channel types
|
||||||
|
|
||||||
|
@ -62,13 +62,13 @@
|
|||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="connectionTimeout" type="integer" min="1" unit="s">
|
<parameter name="connectionTimeout" type="integer" min="1" unit="s">
|
||||||
<label>Connection Timeout</label>
|
<label>Connection Timeout</label>
|
||||||
<description>Connection timeout for FTP request, sec</description>
|
<description>Connection timeout for FTP request, in seconds</description>
|
||||||
<default>30</default>
|
<default>30</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="pollInterval" type="integer" min="1" unit="s">
|
<parameter name="pollInterval" type="integer" min="1" unit="s">
|
||||||
<label>Polling Interval</label>
|
<label>Polling Interval</label>
|
||||||
<description>Interval for polling folder changes, sec</description>
|
<description>Interval for polling folder changes, in seconds</description>
|
||||||
<default>60</default>
|
<default>60</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
@ -105,7 +105,7 @@
|
|||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="pollIntervalLocal" type="integer" min="1" unit="s">
|
<parameter name="pollIntervalLocal" type="integer" min="1" unit="s">
|
||||||
<label>Polling Interval</label>
|
<label>Polling Interval</label>
|
||||||
<description>Interval for polling folder changes, sec</description>
|
<description>Interval for polling folder changes, in seconds</description>
|
||||||
<default>60</default>
|
<default>60</default>
|
||||||
<advanced>true</advanced>
|
<advanced>true</advanced>
|
||||||
</parameter>
|
</parameter>
|
||||||
@ -123,4 +123,50 @@
|
|||||||
</parameter>
|
</parameter>
|
||||||
</config-description>
|
</config-description>
|
||||||
</thing-type>
|
</thing-type>
|
||||||
|
<thing-type id="s3bucket">
|
||||||
|
<label>AWS S3 Bucket</label>
|
||||||
|
<description>AWS S3 bucket to be watched</description>
|
||||||
|
|
||||||
|
<channels>
|
||||||
|
<channel id="newfile" typeId="newfile-channel"/>
|
||||||
|
</channels>
|
||||||
|
|
||||||
|
<config-description>
|
||||||
|
<parameter name="s3BucketName" type="text" required="true">
|
||||||
|
<label>S3 Bucket Name</label>
|
||||||
|
<description>Name of the S3 bucket to be watched</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="s3Path" type="text">
|
||||||
|
<label>S3 Path</label>
|
||||||
|
<description>S3 path (folder) to be monitored</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="awsRegion" type="text" required="true">
|
||||||
|
<label>AWS Region</label>
|
||||||
|
<description>AWS region of S3 bucket</description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="pollIntervalS3" type="integer" min="1" unit="s">
|
||||||
|
<label>Polling Interval</label>
|
||||||
|
<description>Interval for polling S3 bucket changes, in seconds</description>
|
||||||
|
<default>60</default>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="s3Anonymous" type="boolean">
|
||||||
|
<label>Anonymous Connection</label>
|
||||||
|
<default>false</default>
|
||||||
|
<description>Connect anonymously (works for public buckets)</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="awsKey" type="text">
|
||||||
|
<label>AWS Access Key</label>
|
||||||
|
<description>AWS access key</description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="awsSecret" type="text">
|
||||||
|
<label>AWS Secret</label>
|
||||||
|
<description>AWS secret</description>
|
||||||
|
<context>password</context>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</thing-type>
|
||||||
</thing:thing-descriptions>
|
</thing:thing-descriptions>
|
||||||
|
Loading…
Reference in New Issue
Block a user