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            /**
029             * Update interval (ms).
030             */
031            private static final long INTERVAL = 2000;
032    
033            /**
034             * Used to update lock state.
035             * 
036             * @author sarxos
037             */
038            private class LockUpdater extends Thread {
039    
040                    public LockUpdater() {
041                            super();
042                            setName(String.format("webcam-lock-[%s]", webcam.getName()));
043                            setDaemon(true);
044                            setUncaughtExceptionHandler(WebcamExceptionHandler.getInstance());
045                    }
046    
047                    @Override
048                    public void run() {
049                            do {
050                                    update();
051                                    try {
052                                            Thread.sleep(INTERVAL);
053                                    } catch (InterruptedException e) {
054                                            LOG.debug("Lock updater has been interrupted");
055                                            return;
056                                    }
057                            } while (locked.get());
058                    }
059    
060            }
061    
062            /**
063             * And the Webcam we will be locking.
064             */
065            private final Webcam webcam;
066    
067            private Thread updater = null;
068    
069            private AtomicBoolean locked = new AtomicBoolean(false);
070    
071            private File lock = null;
072    
073            /**
074             * Creates global webcam lock.
075             * 
076             * @param webcam the webcam instance to be locked
077             */
078            protected WebcamLock(Webcam webcam) {
079                    super();
080                    this.webcam = webcam;
081                    this.lock = new File(System.getProperty("java.io.tmpdir"), getLockName());
082            }
083    
084            private String getLockName() {
085                    return String.format(".webcam-lock-%d", Math.abs(webcam.getName().hashCode()));
086            }
087    
088            private void write(long value) {
089    
090                    String name = getLockName();
091    
092                    File tmp = null;
093                    DataOutputStream dos = null;
094    
095                    try {
096                            tmp = File.createTempFile(name, "");
097    
098                            dos = new DataOutputStream(new FileOutputStream(tmp));
099                            dos.writeLong(value);
100                            dos.flush();
101    
102                    } catch (IOException e) {
103                            throw new WebcamException(e);
104                    } finally {
105                            if (dos != null) {
106                                    try {
107                                            dos.close();
108                                    } catch (IOException e) {
109                                            throw new WebcamException(e);
110                                    }
111                            }
112                    }
113    
114                    if (!locked.get()) {
115                            return;
116                    }
117    
118                    if (!tmp.renameTo(lock)) {
119                            LOG.warn("Ooops, system was not able to rename lock file from {} to {}", tmp, lock);
120                    }
121            }
122    
123            private long read() {
124                    DataInputStream dis = null;
125                    try {
126                            return (dis = new DataInputStream(new FileInputStream(lock))).readLong();
127                    } catch (IOException e) {
128                            throw new WebcamException(e);
129                    } finally {
130                            if (dis != null) {
131                                    try {
132                                            dis.close();
133                                    } catch (IOException e) {
134                                            throw new WebcamException(e);
135                                    }
136                            }
137                    }
138            }
139    
140            private void update() {
141                    write(System.currentTimeMillis());
142            }
143    
144            /**
145             * Lock webcam.
146             */
147            public void lock() {
148    
149                    if (isLocked()) {
150                            throw new WebcamLockException(String.format("Webcam %s has already been locked", webcam.getName()));
151                    }
152    
153                    if (!locked.compareAndSet(false, true)) {
154                            return;
155                    }
156    
157                    LOG.debug("Lock {}", webcam);
158    
159                    update();
160    
161                    updater = new LockUpdater();
162                    updater.start();
163            }
164    
165            /**
166             * Unlock webcam.
167             */
168            public void unlock() {
169    
170                    if (!locked.compareAndSet(true, false)) {
171                            return;
172                    }
173    
174                    LOG.debug("Unlock {}", webcam);
175    
176                    updater.interrupt();
177    
178                    write(-1);
179    
180                    if (!lock.delete()) {
181                            lock.deleteOnExit();
182                    }
183            }
184    
185            /**
186             * Check if webcam is locked.
187             * 
188             * @return True if webcam is locked, false otherwise
189             */
190            public boolean isLocked() {
191    
192                    // check if locked by current process
193    
194                    if (locked.get()) {
195                            return true;
196                    }
197    
198                    // check if locked by other process
199    
200                    if (!lock.exists()) {
201                            return false;
202                    }
203    
204                    long now = System.currentTimeMillis();
205                    long tsp = read();
206    
207                    LOG.trace("Lock timestamp {} now {} for {}", tsp, now, webcam);
208    
209                    if (tsp > now - INTERVAL * 2) {
210                            return true;
211                    }
212    
213                    return false;
214            }
215    }