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}