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    }