Skip to content

Commit

Permalink
Add discovery support to IP camera driver, closes #214
Browse files Browse the repository at this point in the history
  • Loading branch information
sarxos committed Jun 11, 2014
1 parent 10ddd52 commit addb80d
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.apache.http.auth.AuthScope;
import org.apache.http.client.AuthCache;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpHead;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
Expand Down Expand Up @@ -118,6 +119,7 @@ public void run() {

// case when someone manually closed stream, do not log
// exception, this is normal behavior

if (stream.isClosed()) {
LOG.debug("Stream already closed, returning");
return;
Expand Down Expand Up @@ -393,6 +395,40 @@ private BufferedImage getImagePullMode() {
}
}

/**
* This method will send HTTP HEAD request to the camera URL to check
* whether it's online or offline. It's online when this request succeed and
* it's offline if any exception occurs or response code is 404 Not Found.
*
* @return True if camera is online, false otherwise
*/
public boolean isOnline() {

LOG.debug("Checking online status for {} at {}", getName(), getURL());

URI uri = null;
try {
uri = getURL().toURI();
} catch (URISyntaxException e) {
throw new WebcamException(String.format("Incorrect URI syntax '%s'", uri), e);
}

HttpHead head = new HttpHead(uri);

HttpResponse response = null;
try {
response = client.execute(head);
} catch (Exception e) {
return false;
} finally {
if (head != null) {
head.releaseConnection();
}
}

return response.getStatusLine().getStatusCode() != 404;
}

@Override
public void open() {
if (disposed) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,193 @@
package com.github.sarxos.webcam.ds.ipcam;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.sarxos.webcam.WebcamDevice;
import com.github.sarxos.webcam.WebcamDiscoverySupport;
import com.github.sarxos.webcam.WebcamDriver;
import com.github.sarxos.webcam.WebcamExceptionHandler;


/**
* IP camera driver.
*
* @author Bartosz Firyn (SarXos)
* @author Bartosz Firyn (sarxos)
*/
public class IpCamDriver implements WebcamDriver {
public class IpCamDriver implements WebcamDriver, WebcamDiscoverySupport {

/**
* Thread factory.
*
* @author Bartosz Firyn (sarxos)
*/
private static class DeviceCheckThreadFactory implements ThreadFactory {

/**
* Next number for created thread.
*/
private AtomicInteger number = new AtomicInteger();

@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, "online-check-" + number.incrementAndGet());
t.setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
t.setDaemon(true);
return t;
}
}

/**
* Logger.
*/
private static final Logger LOG = LoggerFactory.getLogger(IpCamDriver.class);

/**
* Thread factory.
*/
private static final ThreadFactory THREAD_FACTORY = new DeviceCheckThreadFactory();

/**
* The callable to query single IP camera device. Callable getter will
* return device if it's online or null if it's offline.
*
* @author Bartosz Firyn (sarxos)
*/
private static class DeviceOnlineCheck implements Callable<IpCamDevice> {

/**
* IP camera device.
*/
private final IpCamDevice device;

private final CountDownLatch latch;

/**
* The callable to query single IP camera device.
*
* @param device the device to check online status
*/
public DeviceOnlineCheck(IpCamDevice device, CountDownLatch latch) {
this.device = device;
this.latch = latch;
}

@Override
public IpCamDevice call() throws Exception {
try {
return device.isOnline() ? device : null;
} finally {
latch.countDown();
}
}
}

/**
* Discovery scan interval in milliseconds.
*/
private volatile long scanInterval = 10000;

/**
* Discovery scan timeout in milliseconds. This is maximum time which
* executor will wait for online detection to succeed.
*/
private volatile long scanTimeout = 10000;

/**
* Is discovery scanning possible.
*/
private volatile boolean scanning = false;

/**
* Execution service.
*/
private final ExecutorService executor = Executors.newCachedThreadPool(THREAD_FACTORY);

public IpCamDriver() {
this(null);
this(null, false);
}

public IpCamDriver(boolean scanning) {
this(null, scanning);
}

public IpCamDriver(IpCamStorage storage) {
this(storage, false);
}

public IpCamDriver(IpCamStorage storage, boolean scanning) {
if (storage != null) {
storage.open();
}
this.scanning = scanning;
}

@Override
public List<WebcamDevice> getDevices() {
return Collections.unmodifiableList((List<? extends WebcamDevice>) IpCamDeviceRegistry.getIpCameras());

// in case when scanning is disabled (by default) this method will
// return all registered devices

if (!isScanPossible()) {
return Collections.unmodifiableList((List<? extends WebcamDevice>) IpCamDeviceRegistry.getIpCameras());
}

// if scanning is enabled, this method will first perform HTTP lookup
// for every IP camera device and only online devices will be returned

List<IpCamDevice> devices = IpCamDeviceRegistry.getIpCameras();
CountDownLatch latch = new CountDownLatch(devices.size());
List<Future<IpCamDevice>> futures = new ArrayList<Future<IpCamDevice>>(devices.size());

for (IpCamDevice device : devices) {
futures.add(executor.submit(new DeviceOnlineCheck(device, latch)));
}

try {
if (!latch.await(scanTimeout, TimeUnit.MILLISECONDS)) {
for (Future<IpCamDevice> future : futures) {
if (!future.isDone()) {
future.cancel(true);
}
}
}
} catch (InterruptedException e1) {
return null;
}

List<IpCamDevice> online = new ArrayList<IpCamDevice>(devices.size());

for (Future<IpCamDevice> future : futures) {

IpCamDevice device = null;
try {
if ((device = future.get()) != null) {
online.add(device);
}
} catch (InterruptedException e) {
LOG.debug(e.getMessage(), e);
} catch (CancellationException e) {
continue;
} catch (ExecutionException e) {
LOG.error(e.getMessage(), e);
}
}

return Collections.unmodifiableList((List<? extends WebcamDevice>) online);
}

public void register(IpCamDevice device) {
Expand All @@ -46,4 +207,57 @@ public boolean isThreadSafe() {
public String toString() {
return getClass().getSimpleName();
}

@Override
public long getScanInterval() {
return scanInterval;
}

/**
* Set new scan interval. Value must be given in milliseconds and shall not
* be negative.
*
* @param scanInterval
*/
public void setScanInterval(long scanInterval) {
if (scanInterval > 0) {
this.scanInterval = scanInterval;
} else {
throw new IllegalArgumentException("Scan interval for IP camera cannot be negative");
}
}

@Override
public boolean isScanPossible() {
return scanning;
}

/**
* Set discovery scanning possible.
*
* @param scanning
*/
public void setScanPossible(boolean scanning) {
this.scanning = scanning;
}

/**
* @return Scan timeout in milliseconds
*/
public long getScanTimeout() {
return scanTimeout;
}

/**
* Set new scan timeout. This value cannot be less than 1000 milliseconds
* (which equals 1 second).
*
* @param scanTimeout the scan timeout in milliseconds
*/
public void setScanTimeout(long scanTimeout) {
if (scanTimeout < 1000) {
scanTimeout = 1000;
}
this.scanTimeout = scanTimeout;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public Thread newThread(Runnable r) {
private volatile List<Webcam> webcams = null;

private AtomicBoolean running = new AtomicBoolean(false);
private AtomicBoolean enabled = new AtomicBoolean(false);
private AtomicBoolean enabled = new AtomicBoolean(true);

private Thread runner = null;

Expand Down

0 comments on commit addb80d

Please sign in to comment.