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}