]> git.basschouten.com Git - openhab-addons.git/blob
e057def94a281be897315d092feef763f63dc3b3
[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         }
112         if (initJob != null) {
113             initJob.cancel(true);
114         }
115         if (ftp.isConnected()) {
116             try {
117                 ftp.logout();
118                 ftp.disconnect();
119             } catch (IOException e) {
120                 logger.debug("Error terminating FTP connection: ", e);
121             }
122         }
123     }
124
125     private void listDirectory(FTPClient ftpClient, String dirPath, boolean recursive, List<String> dirFiles)
126             throws IOException {
127         Instant dateNow = Instant.now();
128         for (FTPFile file : ftpClient.listFiles(dirPath)) {
129             String currentFileName = file.getName();
130             if (currentFileName.equals(".") || currentFileName.equals("..")) {
131                 continue;
132             }
133             String filePath = dirPath + "/" + currentFileName;
134             if (file.isDirectory()) {
135                 if (recursive) {
136                     try {
137                         listDirectory(ftpClient, filePath, recursive, dirFiles);
138                     } catch (IOException e) {
139                         logger.debug("Can't read FTP directory: {}", filePath, e);
140                     }
141                 }
142             } else {
143                 long diff = ChronoUnit.HOURS.between(file.getTimestamp().toInstant(), dateNow);
144                 if (diff < config.diffHours) {
145                     dirFiles.add("ftp:/" + ftpClient.getRemoteAddress() + filePath);
146                 }
147             }
148         }
149     }
150
151     private void connectionKeepAlive() {
152         if (!ftp.isConnected()) {
153             switch (config.secureMode) {
154                 case NONE:
155                     ftp = new FTPClient();
156                     break;
157                 case IMPLICIT:
158                     ftp = new FTPSClient(true);
159                     break;
160                 case EXPLICIT:
161                     ftp = new FTPSClient(false);
162                     break;
163             }
164
165             int reply = 0;
166             ftp.setListHiddenFiles(config.listHidden);
167             ftp.setConnectTimeout(config.connectionTimeout * 1000);
168
169             try {
170                 ftp.connect(config.ftpAddress, config.ftpPort);
171                 reply = ftp.getReplyCode();
172
173                 if (!FTPReply.isPositiveCompletion(reply)) {
174                     ftp.disconnect();
175                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
176                             "FTP server refused connection.");
177                     return;
178                 }
179             } catch (IOException e) {
180                 if (ftp.isConnected()) {
181                     try {
182                         ftp.disconnect();
183                     } catch (IOException e2) {
184                         logger.debug("Error disconneting, lost connection? : {}", e2.getMessage());
185                     }
186                 }
187                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
188                 return;
189             }
190             try {
191                 if (!ftp.login(config.ftpUsername, config.ftpPassword)) {
192                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, ftp.getReplyString());
193                     ftp.logout();
194                     return;
195                 }
196                 updateStatus(ThingStatus.ONLINE);
197                 ScheduledFuture<?> executionJob = this.executionJob;
198                 if (executionJob != null) {
199                     executionJob.cancel(true);
200                 }
201                 this.executionJob = scheduler.scheduleWithFixedDelay(this::refreshFTPFolderInformation, 0,
202                         config.pollInterval, TimeUnit.SECONDS);
203             } catch (IOException e) {
204                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
205             }
206         }
207     }
208
209     private void refreshFTPFolderInformation() {
210         String ftpRootDir = config.ftpDir;
211         final File currentFtpListingFile = this.currentFtpListingFile;
212         if (ftp.isConnected()) {
213             ftp.enterLocalPassiveMode();
214             try {
215                 if (ftpRootDir.endsWith("/")) {
216                     ftpRootDir = ftpRootDir.substring(0, ftpRootDir.length() - 1);
217                 }
218                 if (!ftpRootDir.startsWith("/")) {
219                     ftpRootDir = "/" + ftpRootDir;
220                 }
221                 List<String> currentFtpListing = new ArrayList<>();
222                 listDirectory(ftp, ftpRootDir, config.listRecursiveFtp, currentFtpListing);
223                 List<String> diffFtpListing = new ArrayList<>(currentFtpListing);
224                 diffFtpListing.removeAll(previousFtpListing);
225                 diffFtpListing.forEach(file -> triggerChannel(CHANNEL_NEWFILE, file));
226                 if (!diffFtpListing.isEmpty() && currentFtpListingFile != null) {
227                     try {
228                         WatcherCommon.saveNewListing(diffFtpListing, currentFtpListingFile);
229                     } catch (IOException e2) {
230                         logger.debug("Can't save new listing into file: {}", e2.getMessage());
231                     }
232                 }
233                 previousFtpListing = new ArrayList<>(currentFtpListing);
234             } catch (IOException e) {
235                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
236                         "FTP connection lost. " + e.getMessage());
237                 try {
238                     ftp.disconnect();
239                 } catch (IOException e1) {
240                     logger.debug("Error disconneting, lost connection? {}", e1.getMessage());
241                 }
242             }
243         } else {
244             logger.debug("FTP connection lost.");
245         }
246     }
247 }