001package com.github.sarxos.webcam.ds.ipcam;
002
003import java.util.ArrayList;
004import java.util.Collections;
005import java.util.List;
006import java.util.concurrent.Callable;
007import java.util.concurrent.CancellationException;
008import java.util.concurrent.CountDownLatch;
009import java.util.concurrent.ExecutionException;
010import java.util.concurrent.ExecutorService;
011import java.util.concurrent.Executors;
012import java.util.concurrent.Future;
013import java.util.concurrent.ThreadFactory;
014import java.util.concurrent.TimeUnit;
015import java.util.concurrent.atomic.AtomicInteger;
016
017import org.slf4j.Logger;
018import org.slf4j.LoggerFactory;
019
020import com.github.sarxos.webcam.WebcamDevice;
021import com.github.sarxos.webcam.WebcamDiscoverySupport;
022import com.github.sarxos.webcam.WebcamDriver;
023import com.github.sarxos.webcam.WebcamExceptionHandler;
024
025
026/**
027 * IP camera driver.
028 *
029 * @author Bartosz Firyn (sarxos)
030 */
031public class IpCamDriver implements WebcamDriver, WebcamDiscoverySupport {
032
033        /**
034         * Thread factory.
035         *
036         * @author Bartosz Firyn (sarxos)
037         */
038        private static class DeviceCheckThreadFactory implements ThreadFactory {
039
040                /**
041                 * Next number for created thread.
042                 */
043                private AtomicInteger number = new AtomicInteger();
044
045                @Override
046                public Thread newThread(Runnable r) {
047                        Thread t = new Thread(r, "online-check-" + number.incrementAndGet());
048                        t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
049                        t.setDaemon(true);
050                        return t;
051                }
052        }
053
054        /**
055         * Logger.
056         */
057        private static final Logger LOG = LoggerFactory.getLogger(IpCamDriver.class);
058
059        /**
060         * Thread factory.
061         */
062        private static final ThreadFactory THREAD_FACTORY = new DeviceCheckThreadFactory();
063
064        /**
065         * The callable to query single IP camera device. Callable getter will return device if it's
066         * online or null if it's offline.
067         *
068         * @author Bartosz Firyn (sarxos)
069         */
070        private static class DeviceOnlineCheck implements Callable<IpCamDevice> {
071
072                /**
073                 * IP camera device.
074                 */
075                private final IpCamDevice device;
076
077                private final CountDownLatch latch;
078
079                /**
080                 * The callable to query single IP camera device.
081                 *
082                 * @param device the device to check online status
083                 * @param latch the count down latch
084                 */
085                public DeviceOnlineCheck(IpCamDevice device, CountDownLatch latch) {
086                        this.device = device;
087                        this.latch = latch;
088                }
089
090                @Override
091                public IpCamDevice call() throws Exception {
092                        try {
093                                return device.isOnline() ? device : null;
094                        } finally {
095                                latch.countDown();
096                        }
097                }
098        }
099
100        /**
101         * Discovery scan interval in milliseconds.
102         */
103        private volatile long scanInterval = 10000;
104
105        /**
106         * Discovery scan timeout in milliseconds. This is maximum time which executor will wait for
107         * online detection to succeed.
108         */
109        private volatile long scanTimeout = 10000;
110
111        /**
112         * Is discovery scanning possible.
113         */
114        private volatile boolean scanning = false;
115
116        /**
117         * Execution service.
118         */
119        private final ExecutorService executor = Executors.newCachedThreadPool(THREAD_FACTORY);
120
121        public IpCamDriver() {
122                this(null, false);
123        }
124
125        public IpCamDriver(boolean scanning) {
126                this(null, scanning);
127        }
128
129        public IpCamDriver(IpCamStorage storage) {
130                this(storage, false);
131        }
132
133        public IpCamDriver(IpCamStorage storage, boolean scanning) {
134                if (storage != null) {
135                        storage.open();
136                }
137                this.scanning = scanning;
138        }
139
140        @Override
141        public List<WebcamDevice> getDevices() {
142
143                // in case when scanning is disabled (by default) this method will
144                // return all registered devices
145
146                if (!isScanPossible()) {
147                        return Collections.unmodifiableList((List<? extends WebcamDevice>) IpCamDeviceRegistry.getIpCameras());
148                }
149
150                // if scanning is enabled, this method will first perform HTTP lookup
151                // for every IP camera device and only online devices will be returned
152
153                List<IpCamDevice> devices = IpCamDeviceRegistry.getIpCameras();
154                CountDownLatch latch = new CountDownLatch(devices.size());
155                List<Future<IpCamDevice>> futures = new ArrayList<Future<IpCamDevice>>(devices.size());
156
157                for (IpCamDevice device : devices) {
158                        futures.add(executor.submit(new DeviceOnlineCheck(device, latch)));
159                }
160
161                try {
162                        if (!latch.await(scanTimeout, TimeUnit.MILLISECONDS)) {
163                                for (Future<IpCamDevice> future : futures) {
164                                        if (!future.isDone()) {
165                                                future.cancel(true);
166                                        }
167                                }
168                        }
169                } catch (InterruptedException e1) {
170                        return null;
171                }
172
173                List<IpCamDevice> online = new ArrayList<IpCamDevice>(devices.size());
174
175                for (Future<IpCamDevice> future : futures) {
176
177                        IpCamDevice device = null;
178                        try {
179                                if ((device = future.get()) != null) {
180                                        online.add(device);
181                                }
182                        } catch (InterruptedException e) {
183                                LOG.debug(e.getMessage(), e);
184                        } catch (CancellationException e) {
185                                continue;
186                        } catch (ExecutionException e) {
187                                LOG.error(e.getMessage(), e);
188                        }
189                }
190
191                return Collections.unmodifiableList((List<? extends WebcamDevice>) online);
192        }
193
194        public void register(IpCamDevice device) {
195                IpCamDeviceRegistry.register(device);
196        }
197
198        public void unregister(IpCamDevice device) {
199                IpCamDeviceRegistry.unregister(device);
200        }
201
202        @Override
203        public boolean isThreadSafe() {
204                return true;
205        }
206
207        @Override
208        public String toString() {
209                return getClass().getSimpleName();
210        }
211
212        @Override
213        public long getScanInterval() {
214                return scanInterval;
215        }
216
217        /**
218         * Set new scan interval. Value must be given in milliseconds and shall not be negative.
219         *
220         * @param scanInterval
221         */
222        public void setScanInterval(long scanInterval) {
223                if (scanInterval > 0) {
224                        this.scanInterval = scanInterval;
225                } else {
226                        throw new IllegalArgumentException("Scan interval for IP camera cannot be negative");
227                }
228        }
229
230        @Override
231        public boolean isScanPossible() {
232                return scanning;
233        }
234
235        /**
236         * Set discovery scanning possible.
237         *
238         * @param scanning
239         */
240        public void setScanPossible(boolean scanning) {
241                this.scanning = scanning;
242        }
243
244        /**
245         * @return Scan timeout in milliseconds
246         */
247        public long getScanTimeout() {
248                return scanTimeout;
249        }
250
251        /**
252         * Set new scan timeout. This value cannot be less than 1000 milliseconds (which equals 1
253         * second).
254         *
255         * @param scanTimeout the scan timeout in milliseconds
256         */
257        public void setScanTimeout(long scanTimeout) {
258                if (scanTimeout < 1000) {
259                        scanTimeout = 1000;
260                }
261                this.scanTimeout = scanTimeout;
262        }
263}