1<?php
2
3namespace Sabre\DAV\Locks\Backend;
4
5use Sabre\DAV\Locks\LockInfo;
6
7/**
8 * This Locks backend stores all locking information in a single file.
9 *
10 * Note that this is not nearly as robust as a database. If you are considering
11 * using this backend, keep in mind that the PDO backend can work with SqLite,
12 * which is designed to be a good file-based database.
13 *
14 * It literally solves the problem this class solves as well, but much better.
15 *
16 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
17 * @author Evert Pot (http://evertpot.com/)
18 * @license http://sabre.io/license/ Modified BSD License
19 */
20class File extends AbstractBackend {
21
22    /**
23     * The storage file
24     *
25     * @var string
26     */
27    private $locksFile;
28
29    /**
30     * Constructor
31     *
32     * @param string $locksFile path to file
33     */
34    function __construct($locksFile) {
35
36        $this->locksFile = $locksFile;
37
38    }
39
40    /**
41     * Returns a list of Sabre\DAV\Locks\LockInfo objects
42     *
43     * This method should return all the locks for a particular uri, including
44     * locks that might be set on a parent uri.
45     *
46     * If returnChildLocks is set to true, this method should also look for
47     * any locks in the subtree of the uri for locks.
48     *
49     * @param string $uri
50     * @param bool $returnChildLocks
51     * @return array
52     */
53    function getLocks($uri, $returnChildLocks) {
54
55        $newLocks = [];
56
57        $locks = $this->getData();
58
59        foreach ($locks as $lock) {
60
61            if ($lock->uri === $uri ||
62                //deep locks on parents
63                ($lock->depth != 0 && strpos($uri, $lock->uri . '/') === 0) ||
64
65                // locks on children
66                ($returnChildLocks && (strpos($lock->uri, $uri . '/') === 0))) {
67
68                $newLocks[] = $lock;
69
70            }
71
72        }
73
74        // Checking if we can remove any of these locks
75        foreach ($newLocks as $k => $lock) {
76            if (time() > $lock->timeout + $lock->created) unset($newLocks[$k]);
77        }
78        return $newLocks;
79
80    }
81
82    /**
83     * Locks a uri
84     *
85     * @param string $uri
86     * @param LockInfo $lockInfo
87     * @return bool
88     */
89    function lock($uri, LockInfo $lockInfo) {
90
91        // We're making the lock timeout 30 minutes
92        $lockInfo->timeout = 1800;
93        $lockInfo->created = time();
94        $lockInfo->uri = $uri;
95
96        $locks = $this->getData();
97
98        foreach ($locks as $k => $lock) {
99            if (
100                ($lock->token == $lockInfo->token) ||
101                (time() > $lock->timeout + $lock->created)
102            ) {
103                unset($locks[$k]);
104            }
105        }
106        $locks[] = $lockInfo;
107        $this->putData($locks);
108        return true;
109
110    }
111
112    /**
113     * Removes a lock from a uri
114     *
115     * @param string $uri
116     * @param LockInfo $lockInfo
117     * @return bool
118     */
119    function unlock($uri, LockInfo $lockInfo) {
120
121        $locks = $this->getData();
122        foreach ($locks as $k => $lock) {
123
124            if ($lock->token == $lockInfo->token) {
125
126                unset($locks[$k]);
127                $this->putData($locks);
128                return true;
129
130            }
131        }
132        return false;
133
134    }
135
136    /**
137     * Loads the lockdata from the filesystem.
138     *
139     * @return array
140     */
141    protected function getData() {
142
143        if (!file_exists($this->locksFile)) return [];
144
145        // opening up the file, and creating a shared lock
146        $handle = fopen($this->locksFile, 'r');
147        flock($handle, LOCK_SH);
148
149        // Reading data until the eof
150        $data = stream_get_contents($handle);
151
152        // We're all good
153        flock($handle, LOCK_UN);
154        fclose($handle);
155
156        // Unserializing and checking if the resource file contains data for this file
157        $data = unserialize($data);
158        if (!$data) return [];
159        return $data;
160
161    }
162
163    /**
164     * Saves the lockdata
165     *
166     * @param array $newData
167     * @return void
168     */
169    protected function putData(array $newData) {
170
171        // opening up the file, and creating an exclusive lock
172        $handle = fopen($this->locksFile, 'a+');
173        flock($handle, LOCK_EX);
174
175        // We can only truncate and rewind once the lock is acquired.
176        ftruncate($handle, 0);
177        rewind($handle);
178
179        fwrite($handle, serialize($newData));
180        flock($handle, LOCK_UN);
181        fclose($handle);
182
183    }
184
185}
186