]> git.basschouten.com Git - openhab-addons.git/blob
88d9f668842c7a9d55ed6bce61f3343cf8049929
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
7  * This program and the accompanying materials are made available under the
8  * terms of the Eclipse Public License 2.0 which is available at
9  * http://www.eclipse.org/legal/epl-2.0
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.folderwatcher.internal.handler;
14
15 import static org.openhab.binding.folderwatcher.internal.FolderWatcherBindingConstants.CHANNEL_NEWFILE;
16
17 import java.io.File;
18 import java.io.IOException;
19 import java.time.Instant;
20 import java.time.temporal.ChronoUnit;
21 import java.util.ArrayList;
22 import java.util.List;
23 import java.util.concurrent.ScheduledFuture;
24 import java.util.concurrent.TimeUnit;
25
26 import org.apache.commons.net.ftp.FTPClient;
27 import org.apache.commons.net.ftp.FTPFile;
28 import org.apache.commons.net.ftp.FTPReply;
29 import org.apache.commons.net.ftp.FTPSClient;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.folderwatcher.internal.common.WatcherCommon;
33 import org.openhab.binding.folderwatcher.internal.config.FtpFolderWatcherConfiguration;
34 import org.openhab.core.OpenHAB;
35 import org.openhab.core.thing.ChannelUID;
36 import org.openhab.core.thing.Thing;
37 import org.openhab.core.thing.ThingStatus;
38 import org.openhab.core.thing.ThingStatusDetail;
39 import org.openhab.core.thing.binding.BaseThingHandler;
40 import org.openhab.core.types.Command;
41 import org.openhab.core.types.RefreshType;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 /**
46  * The {@link FtpFolderWatcherHandler} is responsible for handling commands, which are
47  * sent to one of the channels.
48  *
49  * @author Alexandr Salamatov - Initial contribution
50  */
51 @NonNullByDefault
52 public class FtpFolderWatcherHandler extends BaseThingHandler {
53     private final Logger logger = LoggerFactory.getLogger(FtpFolderWatcherHandler.class);
54     private FtpFolderWatcherConfiguration config = new FtpFolderWatcherConfiguration();
55     private @Nullable File currentFtpListingFile;
56     private @Nullable ScheduledFuture<?> executionJob, initJob;
57     private FTPClient ftp = new FTPClient();
58     private List<String> previousFtpListing = new ArrayList<>();
59
60     public FtpFolderWatcherHandler(Thing thing) {
61         super(thing);
62     }
63
64     @Override
65     public void handleCommand(ChannelUID channelUID, Command command) {
66         logger.debug("Channel {} triggered with command {}", channelUID.getId(), command);
67         if (command instanceof RefreshType) {
68             refreshFTPFolderInformation();
69         }
70     }
71
72     @Override
73     public void initialize() {
74         File currentFtpListingFile;
75         config = getConfigAs(FtpFolderWatcherConfiguration.class);
76         updateStatus(ThingStatus.UNKNOWN);
77         if (config.connectionTimeout <= 0) {
78             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
79                     "Connection timeout can't be negative");
80             return;
81         }
82         if (config.ftpPort < 0) {
83             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "FTP port can't be negative");
84             return;
85         }
86         if (config.pollInterval <= 0) {
87             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
88                     "Polling interval can't be null or negative");
89         }
90
91         currentFtpListingFile = new File(OpenHAB.getUserDataFolder() + File.separator + "FolderWatcher" + File.separator
92                 + thing.getUID().getAsString().replace(':', '_') + ".data");
93         try {
94             this.currentFtpListingFile = currentFtpListingFile;
95             previousFtpListing = WatcherCommon.initStorage(currentFtpListingFile, config.ftpAddress + config.ftpDir);
96         } catch (IOException e) {
97             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
98             logger.debug("Can't write file {}, error message {}", currentFtpListingFile, e.getMessage());
99             return;
100         }
101         this.initJob = scheduler.scheduleWithFixedDelay(this::connectionKeepAlive, 0, config.pollInterval,
102                 TimeUnit.SECONDS);
103     }
104
105     @Override
106     public void dispose() {
107         ScheduledFuture<?> executionJob = this.executionJob;
108         ScheduledFuture<?> initJob = this.initJob;
109         if (executionJob != null) {
110             executionJob.cancel(true);
111             this.executionJob = null;
112         }
113         if (initJob != null) {
114             initJob.cancel(true);
115             this.initJob = null;
116         }
117         if (ftp.isConnected()) {
118             try {
119                 ftp.logout();
120                 ftp.disconnect();
121             } catch (IOException e) {
122                 logger.debug("Error terminating FTP connection: ", e);
123             }
124         }
125     }
126
127     private void listDirectory(FTPClient ftpClient, String dirPath, boolean recursive, List<String> dirFiles)
128             throws IOException {
129         Instant dateNow = Instant.now();
130         for (FTPFile file : ftpClient.listFiles(dirPath)) {
131             String currentFileName = file.getName();
132             if (currentFileName.equals(".") || currentFileName.equals("..")) {
133                 continue;
134             }
135             String filePath = dirPath + "/" + currentFileName;
136             if (file.isDirectory()) {
137                 if (recursive) {
138                     try {
139                         listDirectory(ftpClient, filePath, recursive, dirFiles);
140                     } catch (IOException e) {
141                         logger.debug("Can't read FTP directory: {}", filePath, e);
142                     }
143                 }
144             } else {
145                 long diff = ChronoUnit.HOURS.between(file.getTimestamp().toInstant(), dateNow);
146                 if (diff < config.diffHours) {
147                     dirFiles.add("ftp:/" + ftpClient.getRemoteAddress() + filePath);
148                 }
149             }
150         }
151     }
152
153     private void connectionKeepAlive() {
154         if (!ftp.isConnected()) {
155             switch (config.secureMode) {
156                 case NONE:
157                     ftp = new FTPClient();
158                     break;
159                 case IMPLICIT:
160                     ftp = new FTPSClient(true);
161                     break;
162                 case EXPLICIT:
163                     ftp = new FTPSClient(false);
164                     break;
165             }
166
167             int reply = 0;
168             ftp.setListHiddenFiles(config.listHidden);
169             ftp.setConnectTimeout(config.connectionTimeout * 1000);
170
171             try {
172                 ftp.connect(config.ftpAddress, config.ftpPort);
173                 reply = ftp.getReplyCode();
174
175                 if (!FTPReply.isPositiveCompletion(reply)) {
176                     ftp.disconnect();
177                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
178                             "FTP server refused connection.");
179                     return;
180                 }
181             } catch (IOException e) {
182                 if (ftp.isConnected()) {
183                     try {
184                         ftp.disconnect();
185                     } catch (IOException e2) {
186                         logger.debug("Error disconneting, lost connection? : {}", e2.getMessage());
187                     }
188                 }
189                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
190                 return;
191             }
192             try {
193                 if (!ftp.login(config.ftpUsername, config.ftpPassword)) {
194                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ftp.getReplyString());
195                     ftp.logout();
196                     return;
197                 }
198                 updateStatus(ThingStatus.ONLINE);
199                 ScheduledFuture<?> executionJob = this.executionJob;
200                 if (executionJob != null) {
201                     executionJob.cancel(true);
202                 }
203                 this.executionJob = scheduler.scheduleWithFixedDelay(this::refreshFTPFolderInformation, 0,
204                         config.pollInterval, TimeUnit.SECONDS);
205             } catch (IOException e) {
206                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
207             }
208         }
209     }
210
211     private void refreshFTPFolderInformation() {
212         String ftpRootDir = config.ftpDir;
213         final File currentFtpListingFile = this.currentFtpListingFile;
214         if (ftp.isConnected()) {
215             ftp.enterLocalPassiveMode();
216             try {
217                 if (ftpRootDir.endsWith("/")) {
218                     ftpRootDir = ftpRootDir.substring(0, ftpRootDir.length() - 1);
219                 }
220                 if (!ftpRootDir.startsWith("/")) {
221                     ftpRootDir = "/" + ftpRootDir;
222                 }
223                 List<String> currentFtpListing = new ArrayList<>();
224                 listDirectory(ftp, ftpRootDir, config.listRecursiveFtp, currentFtpListing);
225                 List<String> diffFtpListing = new ArrayList<>(currentFtpListing);
226                 diffFtpListing.removeAll(previousFtpListing);
227                 diffFtpListing.forEach(file -> triggerChannel(CHANNEL_NEWFILE, file));
228                 if (!diffFtpListing.isEmpty() && currentFtpListingFile != null) {
229                     try {
230                         WatcherCommon.saveNewListing(diffFtpListing, currentFtpListingFile);
231                     } catch (IOException e2) {
232                         logger.debug("Can't save new listing into file: {}", e2.getMessage());
233                     }
234                 }
235                 previousFtpListing = new ArrayList<>(currentFtpListing);
236             } catch (IOException e) {
237                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
238                         "FTP connection lost. " + e.getMessage());
239                 try {
240                     ftp.disconnect();
241                 } catch (IOException e1) {
242                     logger.debug("Error disconneting, lost connection? {}", e1.getMessage());
243                 }
244             }
245         } else {
246             logger.debug("FTP connection lost.");
247         }
248     }
249 }