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
|
|
|
* @property-read string $root |
18
|
|
|
* @property-read FileStorageIndex $index |
19
|
|
|
*/ |
20
|
|
|
class FileStorage |
21
|
|
|
{ |
22
|
|
|
use AccessorTrait; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* @var string |
26
|
|
|
*/ |
27
|
|
|
private $root; |
28
|
|
|
|
29
|
|
|
protected function get_root() |
30
|
|
|
{ |
31
|
|
|
return $this->root; |
32
|
|
|
} |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* @var FileStorageIndex |
36
|
|
|
*/ |
37
|
|
|
private $index; |
38
|
|
|
|
39
|
|
|
protected function get_index() |
40
|
|
|
{ |
41
|
|
|
return $this->index; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
/** |
45
|
|
|
* @param string $root |
46
|
|
|
* @param FileStorageIndex $index |
47
|
|
|
*/ |
48
|
|
|
public function __construct($root, FileStorageIndex $index) |
49
|
|
|
{ |
50
|
|
|
if (!$root) |
51
|
|
|
{ |
52
|
|
|
throw new \InvalidArgumentException("`\$root` parameter cannot be empty."); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
$this->root = rtrim($root, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; |
56
|
|
|
$this->index = $index; |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Adds a reference. |
61
|
|
|
* |
62
|
|
|
* @param int $nid |
63
|
|
|
* @param string $uuid |
64
|
|
|
* @param string $hash |
65
|
|
|
* |
66
|
|
|
* @return IndexKey |
67
|
|
|
*/ |
68
|
|
|
public function index($nid, $uuid, $hash) |
69
|
|
|
{ |
70
|
|
|
$this->assert_hash_is_used($hash); |
71
|
|
|
|
72
|
|
|
$key = IndexKey::from([ $nid, $uuid, $hash ]); |
73
|
|
|
|
74
|
|
|
$this->index->add($key); |
75
|
|
|
|
76
|
|
|
return $key; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* Adds a file. |
81
|
|
|
* |
82
|
|
|
* @param string $source Absolute path to the source file. |
83
|
|
|
* |
84
|
|
|
* @return Pathname |
85
|
|
|
*/ |
86
|
|
|
public function add($source) |
87
|
|
|
{ |
88
|
|
|
$hash = $this->hash($source); |
89
|
|
|
$pathname = $this->find_by_hash($hash); |
90
|
|
|
|
91
|
|
|
if ($pathname) |
92
|
|
|
{ |
93
|
|
|
return $pathname; |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
$pathname = $this->create_pathname($source, $hash); |
97
|
|
|
|
98
|
|
|
copy($source, $pathname); |
99
|
|
|
|
100
|
|
|
return $pathname; |
101
|
|
|
} |
102
|
|
|
|
103
|
|
|
/** |
104
|
|
|
* Releases a reference to a file. |
105
|
|
|
* |
106
|
|
|
* If a file has no reference left it is deleted. |
107
|
|
|
* |
108
|
|
|
* @param $key_or_id_or_uuid_or_hash |
109
|
|
|
*/ |
110
|
|
|
public function release($key_or_id_or_uuid_or_hash) |
111
|
|
|
{ |
112
|
|
|
$index = $this->index; |
113
|
|
|
$matches = $index->find($key_or_id_or_uuid_or_hash); |
114
|
|
|
|
115
|
|
|
foreach ($matches as $key) |
116
|
|
|
{ |
117
|
|
|
$index->delete($key); |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
foreach ($matches as $key) |
121
|
|
|
{ |
122
|
|
|
$hash = $key->hash; |
123
|
|
|
|
124
|
|
|
if ($index->find($hash)) |
125
|
|
|
{ |
126
|
|
|
continue; |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
$pathname = $this->find_by_hash($hash); |
130
|
|
|
|
131
|
|
|
unlink($pathname); |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Finds a file. |
137
|
|
|
* |
138
|
|
|
* @param IndexKey|int|string $key_or_nid_or_uuid_or_hash |
139
|
|
|
* |
140
|
|
|
* @return Pathname|null |
141
|
|
|
*/ |
142
|
|
|
public function find($key_or_nid_or_uuid_or_hash) |
143
|
|
|
{ |
144
|
|
|
$matches = $this->index->find($key_or_nid_or_uuid_or_hash); |
145
|
|
|
|
146
|
|
|
if (!$matches) |
|
|
|
|
147
|
|
|
{ |
148
|
|
|
return null; |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
/* @var $key IndexKey */ |
152
|
|
|
|
153
|
|
|
$key = reset($matches); |
154
|
|
|
|
155
|
|
|
return $this->find_by_hash($key->hash); |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
/** |
159
|
|
|
* Finds a file using its hash. |
160
|
|
|
* |
161
|
|
|
* @param string $hash A hash from {@link Pathname::hash()}. |
162
|
|
|
* |
163
|
|
|
* @return Pathname|null |
164
|
|
|
*/ |
165
|
|
|
protected function find_by_hash($hash) |
166
|
|
|
{ |
167
|
|
|
$di = new \DirectoryIterator($this->root); |
168
|
|
|
$di = new \RegexIterator($di, '#^' . preg_quote($hash) . '\-#'); |
169
|
|
|
|
170
|
|
|
foreach ($di as $file) |
171
|
|
|
{ |
172
|
|
|
return Pathname::from($file->getPathname()); |
173
|
|
|
} |
174
|
|
|
|
175
|
|
|
return null; |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
/** |
179
|
|
|
* Hashes a file. |
180
|
|
|
* |
181
|
|
|
* The file is hashed with {@link Pathname::hash()}. |
182
|
|
|
* |
183
|
|
|
* @param string $pathname Absolute pathname to the file. |
184
|
|
|
* |
185
|
|
|
* @return string The hash of the file. |
186
|
|
|
* |
187
|
|
|
* @throws \LogicException if the file cannot be hashed. |
188
|
|
|
*/ |
189
|
|
|
public function hash($pathname) |
190
|
|
|
{ |
191
|
|
|
return Pathname::hash($pathname); |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
/** |
195
|
|
|
* Creates hash pathname. |
196
|
|
|
* |
197
|
|
|
* @param string $pathname |
198
|
|
|
* @param string $hash A hash from {@link Pathname::hash()}. If empty a hash is computed from |
199
|
|
|
* the file. |
200
|
|
|
* |
201
|
|
|
* @return Pathname |
202
|
|
|
*/ |
203
|
|
|
public function create_pathname($pathname, $hash = null) |
204
|
|
|
{ |
205
|
|
|
$hash = $hash ?: $this->hash($pathname); |
206
|
|
|
|
207
|
|
|
return Pathname::from([ $this->root, $hash ]); |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* Asserts that a hash is used by a file. |
212
|
|
|
* |
213
|
|
|
* @param string $hash |
214
|
|
|
*/ |
215
|
|
|
protected function assert_hash_is_used($hash) |
216
|
|
|
{ |
217
|
|
|
if (!$this->find_by_hash($hash)) |
218
|
|
|
{ |
219
|
|
|
throw new \LogicException("No file matches the hash `$hash`."); |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
} |
223
|
|
|
|
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.