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 }