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
066         * return device if it's 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                 */
084                public DeviceOnlineCheck(IpCamDevice device, CountDownLatch latch) {
085                        this.device = device;
086                        this.latch = latch;
087                }
088
089                @Override
090                public IpCamDevice call() throws Exception {
091                        try {
092                                return device.isOnline() ? device : null;
093                        } finally {
094                                latch.countDown();
095                        }
096                }
097        }
098
099        /**
100         * Discovery scan interval in milliseconds.
101         */
102        private volatile long scanInterval = 10000;
103
104        /**
105         * Discovery scan timeout in milliseconds. This is maximum time which
106         * executor will wait for online detection to succeed.
107         */
108        private volatile long scanTimeout = 10000;
109
110        /**
111         * Is discovery scanning possible.
112         */
113        private volatile boolean scanning = false;
114
115        /**
116         * Execution service.
117         */
118        private final ExecutorService executor = Executors.newCachedThreadPool(THREAD_FACTORY);
119
120        public IpCamDriver() {
121                this(null, false);
122        }
123
124        public IpCamDriver(boolean scanning) {
125                this(null, scanning);
126        }
127
128        public IpCamDriver(IpCamStorage storage) {
129                this(storage, false);
130        }
131
132        public IpCamDriver(IpCamStorage storage, boolean scanning) {
133                if (storage != null) {
134                        storage.open();
135                }
136                this.scanning = scanning;
137        }
138
139        @Override
140        public List<WebcamDevice> getDevices() {
141
142                // in case when scanning is disabled (by default) this method will
143                // return all registered devices
144
145                if (!isScanPossible()) {
146                        return Collections.unmodifiableList((List<? extends WebcamDevice>) IpCamDeviceRegistry.getIpCameras());
147                }
148
149                // if scanning is enabled, this method will first perform HTTP lookup
150                // for every IP camera device and only online devices will be returned
151
152                List<IpCamDevice> devices = IpCamDeviceRegistry.getIpCameras();
153                CountDownLatch latch = new CountDownLatch(devices.size());
154                List<Future<IpCamDevice>> futures = new ArrayList<Future<IpCamDevice>>(devices.size());
155
156                for (IpCamDevice device : devices) {
157                        futures.add(executor.submit(new DeviceOnlineCheck(device, latch)));
158                }
159
160                try {
161                        if (!latch.await(scanTimeout, TimeUnit.MILLISECONDS)) {
162                                for (Future<IpCamDevice> future : futures) {
163                                        if (!future.isDone()) {
164                                                future.cancel(true);
165                                        }
166                                }
167                        }
168                } catch (InterruptedException e1) {
169                        return null;
170                }
171
172                List<IpCamDevice> online = new ArrayList<IpCamDevice>(devices.size());
173
174                for (Future<IpCamDevice> future : futures) {
175
176                        IpCamDevice device = null;
177                        try {
178                                if ((device = future.get()) != null) {
179                                        online.add(device);
180                                }
181                        } catch (InterruptedException e) {
182                                LOG.debug(e.getMessage(), e);
183                        } catch (CancellationException e) {
184                                continue;
185                        } catch (ExecutionException e) {
186                                LOG.error(e.getMessage(), e);
187                        }
188                }
189
190                return Collections.unmodifiableList((List<? extends WebcamDevice>) online);
191        }
192
193        public void register(IpCamDevice device) {
194                IpCamDeviceRegistry.register(device);
195        }
196
197        public void unregister(IpCamDevice device) {
198                IpCamDeviceRegistry.unregister(device);
199        }
200
201        @Override
202        public boolean isThreadSafe() {
203                return true;
204        }
205
206        @Override
207        public String toString() {
208                return getClass().getSimpleName();
209        }
210
211        @Override
212        public long getScanInterval() {
213                return scanInterval;
214        }
215
216        /**
217         * Set new scan interval. Value must be given in milliseconds and shall not
218         * 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
253         * (which equals 1 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}