1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/* |
4
|
|
|
* This file is part of the Icybee package. |
5
|
|
|
* |
6
|
|
|
* (c) Olivier Laviale <[email protected]> |
7
|
|
|
* |
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
9
|
|
|
* file that was distributed with this source code. |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Icybee\Modules\Files\Storage; |
13
|
|
|
|
14
|
|
|
use ICanBoogie\Accessor\AccessorTrait; |
15
|
|
|
|
16
|
|
|
/** |
17
|
|
|
* Am index of the managed files. |
18
|
|
|
* |
19
|
|
|
* @property-read string $root |
20
|
|
|
*/ |
21
|
|
|
class FileStorageIndex |
22
|
|
|
{ |
23
|
|
|
use AccessorTrait; |
24
|
|
|
|
25
|
|
|
const MATCH_BY_KEY = 1; |
26
|
|
|
const MATCH_BY_ID = 2; |
27
|
|
|
const MATCH_BY_UUID = 3; |
28
|
|
|
const MATCH_BY_HASH = 4; |
29
|
|
|
|
30
|
|
|
/** |
31
|
|
|
* Resolves matching type. |
32
|
|
|
* |
33
|
|
|
* @param IndexKey|int|string $key_or_id_or_uuid_or_hash A {@link IndexKey}, a node |
34
|
|
|
* identifier, a v4 UUID, or a hash. |
35
|
|
|
* |
36
|
|
|
* @return int |
37
|
|
|
* |
38
|
|
|
* @throws \InvalidArgumentException if `$key_or_id_or_uuid_or_hash` is not one of |
39
|
|
|
* the required type. |
40
|
|
|
*/ |
41
|
|
|
static private function resolve_matching_type($key_or_id_or_uuid_or_hash) |
42
|
|
|
{ |
43
|
|
|
if ($key_or_id_or_uuid_or_hash instanceof IndexKey) |
44
|
|
|
{ |
45
|
|
|
return self::MATCH_BY_KEY; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
if (is_numeric($key_or_id_or_uuid_or_hash)) |
49
|
|
|
{ |
50
|
|
|
return self::MATCH_BY_ID; |
51
|
|
|
} |
52
|
|
|
|
53
|
|
|
if (strlen($key_or_id_or_uuid_or_hash) === IndexKey::UUID_LENGTH) |
54
|
|
|
{ |
55
|
|
|
return self::MATCH_BY_UUID; |
56
|
|
|
} |
57
|
|
|
|
58
|
|
|
if (strlen($key_or_id_or_uuid_or_hash) === IndexKey::HASH_LENGTH) |
59
|
|
|
{ |
60
|
|
|
return self::MATCH_BY_HASH; |
61
|
|
|
} |
62
|
|
|
|
63
|
|
|
throw new \InvalidArgumentException("Expected IndexKey instance, node identifier, v4 UUID, or a hash. Got: $key_or_id_or_uuid_or_hash"); |
64
|
|
|
} |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Root directory including a trailing `DIRECTORY_SEPARATOR`. |
68
|
|
|
* |
69
|
|
|
* @var string |
70
|
|
|
*/ |
71
|
|
|
private $root; |
72
|
|
|
|
73
|
|
|
protected function get_root() |
74
|
|
|
{ |
75
|
|
|
return $this->root; |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
/** |
79
|
|
|
* @param string $root FileStorageIndex root directory. |
80
|
|
|
*/ |
81
|
|
|
public function __construct($root) |
82
|
|
|
{ |
83
|
|
|
$this->root = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
/** |
87
|
|
|
* Adds a key to the index. |
88
|
|
|
* |
89
|
|
|
* Composite keys using the same `uid` or `uuid` are replaced by this one. |
90
|
|
|
* |
91
|
|
|
* @param IndexKey $key |
92
|
|
|
*/ |
93
|
|
|
public function add(IndexKey $key) |
94
|
|
|
{ |
95
|
|
|
touch($this->root . $key); |
96
|
|
|
|
97
|
|
|
foreach ($this->find_by_id($key->id) as $match) |
98
|
|
|
{ |
99
|
|
|
if ($match === $key) |
100
|
|
|
{ |
101
|
|
|
continue; |
102
|
|
|
} |
103
|
|
|
|
104
|
|
|
$this->delete($match); |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
foreach ($this->find_by_uuid($key->uuid) as $match) |
108
|
|
|
{ |
109
|
|
|
if ($match === $key) |
110
|
|
|
{ |
111
|
|
|
continue; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
$this->delete($match); |
115
|
|
|
} |
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
/** |
119
|
|
|
* Deletes key(s) from the index. |
120
|
|
|
* |
121
|
|
|
* @param IndexKey|int|string $key_or_id_or_uuid_or_hash |
122
|
|
|
* |
123
|
|
|
* @throws \InvalidArgumentException if `$key_or_id_or_uuid_or_hash` is not of the expected type. |
124
|
|
|
*/ |
125
|
|
|
public function delete($key_or_id_or_uuid_or_hash) |
126
|
|
|
{ |
127
|
|
|
$matching = $this->find($key_or_id_or_uuid_or_hash); |
128
|
|
|
|
129
|
|
|
if (!$matching) |
|
|
|
|
130
|
|
|
{ |
131
|
|
|
return; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
foreach ($matching as $match) |
135
|
|
|
{ |
136
|
|
|
unlink($this->root . $match); |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* Finds composite keys. |
142
|
|
|
* |
143
|
|
|
* @param IndexKey|int|string $key_or_id_or_uuid_or_hash |
144
|
|
|
* |
145
|
|
|
* @return IndexKey[] |
146
|
|
|
* |
147
|
|
|
* @throws \InvalidArgumentException if `$key_or_id_or_uuid_or_hash` is not of the expected type. |
148
|
|
|
*/ |
149
|
|
|
public function find($key_or_id_or_uuid_or_hash) |
150
|
|
|
{ |
151
|
|
|
static $methods = [ |
152
|
|
|
|
153
|
|
|
self::MATCH_BY_KEY => 'key', |
154
|
|
|
self::MATCH_BY_ID => 'id', |
155
|
|
|
self::MATCH_BY_UUID => 'uuid', |
156
|
|
|
self::MATCH_BY_HASH => 'hash' |
157
|
|
|
|
158
|
|
|
]; |
159
|
|
|
|
160
|
|
|
$type = $methods[self::resolve_matching_type($key_or_id_or_uuid_or_hash)]; |
161
|
|
|
|
162
|
|
|
return $this->{ 'find_by_' . $type }($key_or_id_or_uuid_or_hash); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
/** |
166
|
|
|
* Returns the composite key matching the composite key. |
167
|
|
|
* |
168
|
|
|
* @param IndexKey|string $key |
169
|
|
|
* |
170
|
|
|
* @return IndexKey[] |
171
|
|
|
*/ |
172
|
|
|
protected function find_by_key($key) |
173
|
|
|
{ |
174
|
|
|
$pathname = $this->root . $key; |
175
|
|
|
|
176
|
|
|
return file_exists($pathname) ? [ $key ] : null; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Returns the composite keys match an identifier. |
181
|
|
|
* |
182
|
|
|
* @param int $id The node identifier to match. |
183
|
|
|
* |
184
|
|
|
* @return IndexKey[] |
185
|
|
|
*/ |
186
|
|
|
protected function find_by_id($id) |
187
|
|
|
{ |
188
|
|
|
return $this->matching('#^' . preg_quote(IndexKey::encode_id($id)) . '\-#'); |
189
|
|
|
} |
190
|
|
|
|
191
|
|
|
/** |
192
|
|
|
* Returns the composite keys match a v4 UUID. |
193
|
|
|
* |
194
|
|
|
* @param string $encoded_uuid The UUID to match. |
195
|
|
|
* |
196
|
|
|
* @return IndexKey[] |
197
|
|
|
*/ |
198
|
|
|
protected function find_by_encoded_uuid($encoded_uuid) |
199
|
|
|
{ |
200
|
|
|
return $this->matching("#^.{" . IndexKey::ENCODED_ID_LENGTH . '}\-' . preg_quote($encoded_uuid) . '\-#'); |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Returns the composite keys match a v4 UUID. |
205
|
|
|
* |
206
|
|
|
* @param string $uuid The UUID to match. |
207
|
|
|
* |
208
|
|
|
* @return IndexKey[] |
209
|
|
|
*/ |
210
|
|
|
protected function find_by_uuid($uuid) |
211
|
|
|
{ |
212
|
|
|
return $this->find_by_encoded_uuid(IndexKey::encode_uuid($uuid)); |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
/** |
216
|
|
|
* Returns the composite keys match a hash. |
217
|
|
|
* |
218
|
|
|
* @param string $hash A hash from {@link Pathname::hash()} |
219
|
|
|
* |
220
|
|
|
* @return IndexKey[] |
221
|
|
|
*/ |
222
|
|
|
protected function find_by_hash($hash) |
223
|
|
|
{ |
224
|
|
|
return $this->matching("#^.{" . IndexKey::ENCODED_ID_LENGTH . '}\-.{' . IndexKey::ENCODED_UUID_LENGTH . '}\-' . preg_quote($hash) . '#'); |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* Returns the composite keys matching a pattern. |
229
|
|
|
* |
230
|
|
|
* @param string $pattern The pattern to match. |
231
|
|
|
* |
232
|
|
|
* @return IndexKey[] |
233
|
|
|
*/ |
234
|
|
|
protected function matching($pattern) |
235
|
|
|
{ |
236
|
|
|
$matches = []; |
237
|
|
|
|
238
|
|
|
$di = new \DirectoryIterator($this->root); |
239
|
|
|
$di = new \RegexIterator($di, $pattern); |
240
|
|
|
|
241
|
|
|
foreach ($di as $match) |
242
|
|
|
{ |
243
|
|
|
$matches[] = IndexKey::from($match->getFilename()); |
244
|
|
|
} |
245
|
|
|
|
246
|
|
|
return $matches; |
247
|
|
|
} |
248
|
|
|
} |
249
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.