Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 129 |
|
0.00% |
0 / 19 |
CRAP | |
0.00% |
0 / 1 |
storage | |
0.00% |
0 / 129 |
|
0.00% |
0 / 19 |
1722 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
get_name | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
get_adapter | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
put_contents | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
get_contents | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
exists | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
12 | |||
delete | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
rename | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
copy | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
read_stream | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
write_stream | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
20 | |||
track_file | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
20 | |||
untrack_file | |
0.00% |
0 / 9 |
|
0.00% |
0 / 1 |
2 | |||
is_tracked | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
track_rename | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
file_size | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
get_size | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
get_num_files | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
free_space | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * |
4 | * This file is part of the phpBB Forum Software package. |
5 | * |
6 | * @copyright (c) phpBB Limited <https://www.phpbb.com> |
7 | * @license GNU General Public License, version 2 (GPL-2.0) |
8 | * |
9 | * For full copyright and license information, please see |
10 | * the docs/CREDITS.txt file. |
11 | * |
12 | */ |
13 | |
14 | namespace phpbb\storage; |
15 | |
16 | use phpbb\cache\driver\driver_interface as cache; |
17 | use phpbb\db\driver\driver_interface as db; |
18 | use phpbb\storage\adapter\adapter_interface; |
19 | use phpbb\storage\exception\storage_exception; |
20 | |
21 | /** |
22 | * Experimental |
23 | */ |
24 | class storage |
25 | { |
26 | /** |
27 | * @var adapter_interface |
28 | */ |
29 | protected $adapter; |
30 | |
31 | /** |
32 | * @var db |
33 | */ |
34 | protected $db; |
35 | |
36 | /** |
37 | * Cache driver |
38 | * @var cache |
39 | */ |
40 | protected $cache; |
41 | |
42 | /** |
43 | * @var adapter_factory |
44 | */ |
45 | protected $factory; |
46 | |
47 | /** |
48 | * @var string |
49 | */ |
50 | protected $storage_name; |
51 | |
52 | /** |
53 | * @var string |
54 | */ |
55 | protected $storage_table; |
56 | |
57 | /** |
58 | * Constructor |
59 | * |
60 | * @param db $db |
61 | * @param cache $cache |
62 | * @param adapter_factory $factory |
63 | * @param string $storage_name |
64 | * @param string $storage_table |
65 | */ |
66 | public function __construct(db $db, cache $cache, adapter_factory $factory, string $storage_name, string $storage_table) |
67 | { |
68 | $this->db = $db; |
69 | $this->cache = $cache; |
70 | $this->factory = $factory; |
71 | $this->storage_name = $storage_name; |
72 | $this->storage_table = $storage_table; |
73 | } |
74 | |
75 | /** |
76 | * Returns storage name |
77 | * |
78 | * @return string |
79 | */ |
80 | public function get_name(): string |
81 | { |
82 | return $this->storage_name; |
83 | } |
84 | |
85 | /** |
86 | * Returns an adapter instance |
87 | * |
88 | * @return adapter_interface |
89 | */ |
90 | protected function get_adapter(): mixed |
91 | { |
92 | if ($this->adapter === null) |
93 | { |
94 | $this->adapter = $this->factory->get($this->storage_name); |
95 | } |
96 | |
97 | return $this->adapter; |
98 | } |
99 | |
100 | /** |
101 | * Dumps content into a file |
102 | * |
103 | * @param string $path The file to be written to. |
104 | * @param string $content The data to write into the file. |
105 | * |
106 | * @throws storage_exception When the file already exists |
107 | * When the file cannot be written |
108 | */ |
109 | public function put_contents(string $path, string $content): void |
110 | { |
111 | if ($this->exists($path)) |
112 | { |
113 | throw new storage_exception('STORAGE_FILE_EXISTS', $path); |
114 | } |
115 | |
116 | $this->get_adapter()->put_contents($path, $content); |
117 | $this->track_file($path); |
118 | } |
119 | |
120 | /** |
121 | * Read the contents of a file |
122 | * |
123 | * @param string $path The file to read |
124 | * |
125 | * @return string Returns file contents |
126 | * |
127 | * @throws storage_exception When the file doesn't exist |
128 | * When cannot read file contents |
129 | * |
130 | */ |
131 | public function get_contents(string $path): string |
132 | { |
133 | if (!$this->exists($path)) |
134 | { |
135 | throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); |
136 | } |
137 | |
138 | return $this->get_adapter()->get_contents($path); |
139 | } |
140 | |
141 | /** |
142 | * Checks the existence of files or directories |
143 | * |
144 | * @param string $path file/directory to check |
145 | * @param bool $full_check check in the filesystem too |
146 | * |
147 | * @return bool Returns true if the file/directory exist, false otherwise |
148 | */ |
149 | public function exists(string $path, bool $full_check = false): bool |
150 | { |
151 | return ($this->is_tracked($path) && (!$full_check || $this->get_adapter()->exists($path))); |
152 | } |
153 | |
154 | /** |
155 | * Removes files or directories |
156 | * |
157 | * @param string $path file/directory to remove |
158 | * |
159 | * @throws storage_exception When removal fails |
160 | * When the file doesn't exist |
161 | */ |
162 | public function delete(string $path): void |
163 | { |
164 | if (!$this->exists($path)) |
165 | { |
166 | throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); |
167 | } |
168 | |
169 | $this->get_adapter()->delete($path); |
170 | $this->untrack_file($path); |
171 | } |
172 | |
173 | /** |
174 | * Rename a file or a directory |
175 | * |
176 | * @param string $path_orig The original file/direcotry |
177 | * @param string $path_dest The target file/directory |
178 | * |
179 | * @throws storage_exception When the file doesn't exist |
180 | * When target exists |
181 | * When file/directory cannot be renamed |
182 | */ |
183 | public function rename(string $path_orig, string $path_dest): void |
184 | { |
185 | if (!$this->exists($path_orig)) |
186 | { |
187 | throw new storage_exception('STORAGE_FILE_NO_EXIST', $path_orig); |
188 | } |
189 | |
190 | if ($this->exists($path_dest)) |
191 | { |
192 | throw new storage_exception('STORAGE_FILE_EXISTS', $path_dest); |
193 | } |
194 | |
195 | $this->get_adapter()->rename($path_orig, $path_dest); |
196 | $this->track_rename($path_orig, $path_dest); |
197 | } |
198 | |
199 | /** |
200 | * Copies a file |
201 | * |
202 | * @param string $path_orig The original filename |
203 | * @param string $path_dest The target filename |
204 | * |
205 | * @throws storage_exception When the file doesn't exist |
206 | * When target exists |
207 | * When the file cannot be copied |
208 | */ |
209 | public function copy(string $path_orig, string $path_dest): void |
210 | { |
211 | if (!$this->exists($path_orig)) |
212 | { |
213 | throw new storage_exception('STORAGE_FILE_NO_EXIST', $path_orig); |
214 | } |
215 | |
216 | if ($this->exists($path_dest)) |
217 | { |
218 | throw new storage_exception('STORAGE_FILE_EXISTS', $path_dest); |
219 | } |
220 | |
221 | $this->get_adapter()->copy($path_orig, $path_dest); |
222 | $this->track_file($path_dest); |
223 | } |
224 | |
225 | /** |
226 | * Reads a file as a stream |
227 | * |
228 | * @param string $path File to read |
229 | * |
230 | * @return resource Returns a file pointer |
231 | * @throws storage_exception When the file doesn't exist |
232 | * When unable to open file |
233 | * |
234 | */ |
235 | public function read_stream(string $path) |
236 | { |
237 | if (!$this->exists($path)) |
238 | { |
239 | throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); |
240 | } |
241 | |
242 | $stream = null; |
243 | $adapter = $this->get_adapter(); |
244 | |
245 | if ($adapter instanceof stream_interface) |
246 | { |
247 | $stream = $adapter->read_stream($path); |
248 | } |
249 | else |
250 | { |
251 | // Simulate the stream |
252 | $stream = fopen('php://temp', 'w+b'); |
253 | fwrite($stream, $adapter->get_contents($path)); |
254 | rewind($stream); |
255 | } |
256 | |
257 | return $stream; |
258 | } |
259 | |
260 | /** |
261 | * Writes a new file using a stream |
262 | * |
263 | * @param string $path The target file |
264 | * @param resource $resource The resource |
265 | * |
266 | * @throws storage_exception When the file exist |
267 | * When target file cannot be created |
268 | */ |
269 | public function write_stream(string $path, $resource): void |
270 | { |
271 | if ($this->exists($path)) |
272 | { |
273 | throw new storage_exception('STORAGE_FILE_EXISTS', $path); |
274 | } |
275 | |
276 | if (!is_resource($resource)) |
277 | { |
278 | throw new storage_exception('STORAGE_INVALID_RESOURCE'); |
279 | } |
280 | |
281 | $adapter = $this->get_adapter(); |
282 | |
283 | if ($adapter instanceof stream_interface) |
284 | { |
285 | $adapter->write_stream($path, $resource); |
286 | $this->track_file($path); |
287 | } |
288 | else |
289 | { |
290 | // Simulate the stream |
291 | $adapter->put_contents($path, stream_get_contents($resource)); |
292 | } |
293 | } |
294 | |
295 | /** |
296 | * Track file in database |
297 | * |
298 | * @param string $path The target file |
299 | * @param bool $update Update file size when already tracked |
300 | */ |
301 | public function track_file(string $path, bool $update = false): void |
302 | { |
303 | if (!$this->get_adapter()->exists($path)) |
304 | { |
305 | throw new storage_exception('STORAGE_FILE_NO_EXIST', $path); |
306 | } |
307 | |
308 | $sql_ary = array( |
309 | 'file_path' => $path, |
310 | 'storage' => $this->get_name(), |
311 | ); |
312 | |
313 | // Get file, if exist update filesize, if not add new record |
314 | $sql = 'SELECT * FROM ' . $this->storage_table . ' |
315 | WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); |
316 | $result = $this->db->sql_query($sql); |
317 | $row = $this->db->sql_fetchrow($result); |
318 | $this->db->sql_freeresult($result); |
319 | |
320 | if (!$row) |
321 | { |
322 | $sql_ary['filesize'] = $this->get_adapter()->file_size($path); |
323 | |
324 | $sql = 'INSERT INTO ' . $this->storage_table . $this->db->sql_build_array('INSERT', $sql_ary); |
325 | $this->db->sql_query($sql); |
326 | } |
327 | else if ($update) |
328 | { |
329 | $sql = 'UPDATE ' . $this->storage_table . ' |
330 | SET filesize = ' . $this->get_adapter()->file_size($path) . ' |
331 | WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); |
332 | $this->db->sql_query($sql); |
333 | } |
334 | |
335 | $this->cache->destroy('_storage_' . $this->get_name() . '_totalsize'); |
336 | $this->cache->destroy('_storage_' . $this->get_name() . '_numfiles'); |
337 | } |
338 | |
339 | /** |
340 | * Untrack file |
341 | * |
342 | * @param string $path The target file |
343 | */ |
344 | public function untrack_file($path) |
345 | { |
346 | $sql_ary = array( |
347 | 'file_path' => $path, |
348 | 'storage' => $this->get_name(), |
349 | ); |
350 | |
351 | $sql = 'DELETE FROM ' . $this->storage_table . ' |
352 | WHERE ' . $this->db->sql_build_array('DELETE', $sql_ary); |
353 | $this->db->sql_query($sql); |
354 | |
355 | $this->cache->destroy('_storage_' . $this->get_name() . '_totalsize'); |
356 | $this->cache->destroy('_storage_' . $this->get_name() . '_numfiles'); |
357 | } |
358 | |
359 | /** |
360 | * Check if a file is tracked |
361 | * |
362 | * @param string $path The file |
363 | * |
364 | * @return bool True if file is tracked |
365 | */ |
366 | public function is_tracked(string $path): bool |
367 | { |
368 | $sql_ary = array( |
369 | 'file_path' => $path, |
370 | 'storage' => $this->get_name(), |
371 | ); |
372 | |
373 | $sql = 'SELECT file_id FROM ' . $this->storage_table . ' |
374 | WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); |
375 | $result = $this->db->sql_query($sql); |
376 | $row = $this->db->sql_fetchrow($result); |
377 | $this->db->sql_freeresult($result); |
378 | |
379 | return $row !== false; |
380 | } |
381 | |
382 | /** |
383 | * Rename tracked file |
384 | * |
385 | * @param string $path_orig The original file/direcotry |
386 | * @param string $path_dest The target file/directory |
387 | */ |
388 | protected function track_rename(string $path_orig, string $path_dest): void |
389 | { |
390 | $sql = 'UPDATE ' . $this->storage_table . " |
391 | SET file_path = '" . $this->db->sql_escape($path_dest) . "' |
392 | WHERE file_path = '" . $this->db->sql_escape($path_orig) . "' |
393 | AND storage = '" . $this->db->sql_escape($this->get_name()) . "'"; |
394 | $this->db->sql_query($sql); |
395 | } |
396 | |
397 | /** |
398 | * Get file size in bytes |
399 | * |
400 | * @param string $path The file |
401 | * |
402 | * @return int Size in bytes. |
403 | * |
404 | * @throws storage_exception When unable to retrieve file size |
405 | */ |
406 | public function file_size(string $path): int |
407 | { |
408 | $sql_ary = array( |
409 | 'file_path' => $path, |
410 | 'storage' => $this->get_name(), |
411 | ); |
412 | |
413 | $sql = 'SELECT filesize FROM ' . $this->storage_table . ' |
414 | WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary); |
415 | $result = $this->db->sql_query($sql); |
416 | $row = $this->db->sql_fetchrow($result); |
417 | $this->db->sql_freeresult($result); |
418 | |
419 | return $row !== false && !empty($row['filesize']) ? $row['filesize'] : $this->get_adapter()->file_size($path); |
420 | } |
421 | |
422 | /** |
423 | * Get total storage size |
424 | * |
425 | * @return int Size in bytes |
426 | */ |
427 | public function get_size(): int |
428 | { |
429 | $total_size = $this->cache->get('_storage_' . $this->get_name() . '_totalsize'); |
430 | |
431 | if ($total_size === false) |
432 | { |
433 | $sql = 'SELECT SUM(filesize) AS totalsize |
434 | FROM ' . $this->storage_table . " |
435 | WHERE storage = '" . $this->db->sql_escape($this->get_name()) . "'"; |
436 | $result = $this->db->sql_query($sql); |
437 | |
438 | $total_size = (int) $this->db->sql_fetchfield('totalsize'); |
439 | $this->cache->put('_storage_' . $this->get_name() . '_totalsize', $total_size); |
440 | |
441 | $this->db->sql_freeresult($result); |
442 | } |
443 | |
444 | return (int) $total_size; |
445 | } |
446 | |
447 | /** |
448 | * Get number of storage files |
449 | * |
450 | * @return int Number of files |
451 | */ |
452 | public function get_num_files(): int |
453 | { |
454 | $number_files = $this->cache->get('_storage_' . $this->get_name() . '_numfiles'); |
455 | |
456 | if ($number_files === false) |
457 | { |
458 | $sql = 'SELECT COUNT(file_id) AS numfiles |
459 | FROM ' . $this->storage_table . " |
460 | WHERE storage = '" . $this->db->sql_escape($this->get_name()) . "'"; |
461 | $result = $this->db->sql_query($sql); |
462 | |
463 | $number_files = (int) $this->db->sql_fetchfield('numfiles'); |
464 | $this->cache->put('_storage_' . $this->get_name() . '_numfiles', $number_files); |
465 | |
466 | $this->db->sql_freeresult($result); |
467 | } |
468 | |
469 | return (int) $number_files; |
470 | } |
471 | |
472 | /** |
473 | * Get space available in bytes |
474 | * |
475 | * @return float Returns available space |
476 | * @throws storage_exception When unable to retrieve available storage space |
477 | * |
478 | */ |
479 | public function free_space() |
480 | { |
481 | return $this->get_adapter()->free_space(); |
482 | } |
483 | |
484 | } |