001 package com.github.sarxos.webcam; 002 003 import java.io.DataInputStream; 004 import java.io.DataOutputStream; 005 import java.io.File; 006 import java.io.FileInputStream; 007 import java.io.FileOutputStream; 008 import java.io.IOException; 009 import java.util.concurrent.atomic.AtomicBoolean; 010 011 import org.slf4j.Logger; 012 import org.slf4j.LoggerFactory; 013 014 015 /** 016 * This class is used as a global (system) lock preventing other processes from 017 * using the same camera while it's open. 018 * 019 * @author Bartosz Firyn (sarxos) 020 */ 021 public class WebcamLock { 022 023 /** 024 * Logger. 025 */ 026 private static final Logger LOG = LoggerFactory.getLogger(WebcamLock.class); 027 028 private static final Object MUTEX = new Object(); 029 030 /** 031 * Update interval (ms). 032 */ 033 private static final long INTERVAL = 2000; 034 035 /** 036 * Used to update lock state. 037 * 038 * @author sarxos 039 */ 040 private class LockUpdater extends Thread { 041 042 public LockUpdater() { 043 super(); 044 setName(String.format("webcam-lock-[%s]", webcam.getName())); 045 setDaemon(true); 046 setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance()); 047 } 048 049 @Override 050 public void run() { 051 do { 052 update(); 053 try { 054 Thread.sleep(INTERVAL); 055 } catch (InterruptedException e) { 056 LOG.debug("Lock updater has been interrupted"); 057 return; 058 } 059 } while (locked.get()); 060 } 061 062 } 063 064 /** 065 * And the Webcam we will be locking. 066 */ 067 private final Webcam webcam; 068 069 private Thread updater = null; 070 071 private AtomicBoolean locked = new AtomicBoolean(false); 072 073 private File lock = null; 074 075 /** 076 * Creates global webcam lock. 077 * 078 * @param webcam the webcam instance to be locked 079 */ 080 protected WebcamLock(Webcam webcam) { 081 super(); 082 this.webcam = webcam; 083 this.lock = new File(System.getProperty("java.io.tmpdir"), getLockName()); 084 } 085 086 private String getLockName() { 087 return String.format(".webcam-lock-%d", Math.abs(webcam.getName().hashCode())); 088 } 089 090 private void write(long value) { 091 092 String name = getLockName(); 093 094 File tmp = null; 095 DataOutputStream dos = null; 096 097 try { 098 tmp = File.createTempFile(name, ""); 099 100 dos = new DataOutputStream(new FileOutputStream(tmp)); 101 dos.writeLong(value); 102 dos.flush(); 103 104 } catch (IOException e) { 105 throw new RuntimeException(e); 106 } finally { 107 if (dos != null) { 108 try { 109 dos.close(); 110 } catch (IOException e) { 111 throw new RuntimeException(e); 112 } 113 } 114 } 115 116 if (!locked.get()) { 117 return; 118 } 119 120 if (tmp.renameTo(lock)) { 121 122 // rename operation can fail (mostly on Windows), so we simply jump 123 // out the method if it succeed, or try to rewrite content using 124 // streams if it fail 125 126 return; 127 } else { 128 129 // create lock file if not exist 130 131 if (!lock.exists()) { 132 try { 133 if (!lock.createNewFile()) { 134 throw new RuntimeException("Not able to create file " + lock); 135 } 136 } catch (IOException e) { 137 throw new RuntimeException(e); 138 } 139 } 140 141 FileOutputStream fos = null; 142 FileInputStream fis = null; 143 144 int k = 0; 145 int n = -1; 146 byte[] buffer = new byte[8]; 147 boolean rewritten = false; 148 149 // rewrite temporary file content to lock, try max 5 times 150 151 synchronized (MUTEX) { 152 do { 153 try { 154 fos = new FileOutputStream(lock); 155 fis = new FileInputStream(tmp); 156 while ((n = fis.read(buffer)) != -1) { 157 fos.write(buffer, 0, n); 158 } 159 rewritten = true; 160 } catch (IOException e) { 161 LOG.debug("Not able to rewrite lock file", e); 162 } finally { 163 if (fos != null) { 164 try { 165 fos.close(); 166 } catch (IOException e) { 167 throw new RuntimeException(e); 168 } 169 } 170 if (fis != null) { 171 try { 172 fis.close(); 173 } catch (IOException e) { 174 throw new RuntimeException(e); 175 } 176 } 177 } 178 if (rewritten) { 179 break; 180 } 181 } while (k++ < 5); 182 } 183 184 if (!rewritten) { 185 throw new WebcamException("Not able to write lock file"); 186 } 187 188 // remove temporary file 189 190 if (!tmp.delete()) { 191 tmp.deleteOnExit(); 192 } 193 } 194 195 } 196 197 private long read() { 198 DataInputStream dis = null; 199 try { 200 return (dis = new DataInputStream(new FileInputStream(lock))).readLong(); 201 } catch (IOException e) { 202 throw new RuntimeException(e); 203 } finally { 204 if (dis != null) { 205 try { 206 dis.close(); 207 } catch (IOException e) { 208 throw new RuntimeException(e); 209 } 210 } 211 } 212 } 213 214 private void update() { 215 write(System.currentTimeMillis()); 216 } 217 218 /** 219 * Lock webcam. 220 */ 221 public void lock() { 222 223 if (isLocked()) { 224 throw new WebcamLockException(String.format("Webcam %s has already been locked", webcam.getName())); 225 } 226 227 if (!locked.compareAndSet(false, true)) { 228 return; 229 } 230 231 LOG.debug("Lock {}", webcam); 232 233 update(); 234 235 updater = new LockUpdater(); 236 updater.start(); 237 } 238 239 /** 240 * Unlock webcam. 241 */ 242 public void unlock() { 243 244 if (!locked.compareAndSet(true, false)) { 245 return; 246 } 247 248 LOG.debug("Unlock {}", webcam); 249 250 updater.interrupt(); 251 252 write(-1); 253 254 if (!lock.delete()) { 255 lock.deleteOnExit(); 256 } 257 } 258 259 /** 260 * Check if webcam is locked. 261 * 262 * @return True if webcam is locked, false otherwise 263 */ 264 public boolean isLocked() { 265 266 // check if locked by current process 267 268 if (locked.get()) { 269 return true; 270 } 271 272 // check if locked by other process 273 274 if (!lock.exists()) { 275 return false; 276 } 277 278 long now = System.currentTimeMillis(); 279 long tsp = read(); 280 281 LOG.trace("Lock timestamp {} now {} for {}", tsp, now, webcam); 282 283 if (tsp > now - INTERVAL * 2) { 284 return true; 285 } 286 287 return false; 288 } 289 }