Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 125
0.00% covered (danger)
0.00%
0 / 20
CRAP
0.00% covered (danger)
0.00%
0 / 1
storage
0.00% covered (danger)
0.00%
0 / 125
0.00% covered (danger)
0.00%
0 / 20
1806
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 get_name
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_adapter
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 put_contents
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 get_contents
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 exists
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
12
 delete
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 rename
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 copy
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 read_stream
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
12
 write_stream
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 track_file
0.00% covered (danger)
0.00%
0 / 24
0.00% covered (danger)
0.00%
0 / 1
20
 untrack_file
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 is_tracked
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 track_rename
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 file_info
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 get_link
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 get_size
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 get_num_files
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 free_space
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
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
14namespace phpbb\storage;
15
16use phpbb\cache\driver\driver_interface as cache;
17use phpbb\db\driver\driver_interface as db;
18use phpbb\storage\adapter\adapter_interface;
19use phpbb\storage\exception\storage_exception;
20
21/**
22 * Experimental
23 */
24class 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, $storage_name, $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()
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()
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($path, $content)
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($path)
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($path, $full_check = false)
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($path)
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($path_orig, $path_dest)
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($path_orig, $path_dest)
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($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($path, $resource)
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($path, $update = false)
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            // Don't call the file_info method, because it check's if the file is tracked
323            // and is not (for now). This method check if the file exists using the adapter
324            // at the beginning.
325            $file = new file_info($this->get_adapter(), $path);
326            $sql_ary['filesize'] = $file->size;
327
328            $sql = 'INSERT INTO ' . $this->storage_table . $this->db->sql_build_array('INSERT', $sql_ary);
329            $this->db->sql_query($sql);
330        }
331        else if ($update)
332        {
333            $file = $this->file_info($path);
334            $sql = 'UPDATE ' . $this->storage_table . '
335                SET filesize = ' . $file->size . '
336                WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary);
337            $this->db->sql_query($sql);
338        }
339
340        $this->cache->destroy('_storage_' . $this->get_name() . '_totalsize');
341        $this->cache->destroy('_storage_' . $this->get_name() . '_numfiles');
342    }
343
344    /**
345     * Untrack file
346     *
347     * @param string    $path        The target file
348     */
349    public function untrack_file($path)
350    {
351        $sql_ary = array(
352            'file_path'        => $path,
353            'storage'        => $this->get_name(),
354        );
355
356        $sql = 'DELETE FROM ' . $this->storage_table . '
357            WHERE ' . $this->db->sql_build_array('DELETE', $sql_ary);
358        $this->db->sql_query($sql);
359
360        $this->cache->destroy('_storage_' . $this->get_name() . '_totalsize');
361        $this->cache->destroy('_storage_' . $this->get_name() . '_numfiles');
362    }
363
364    /**
365     * Check if a file is tracked
366     *
367     * @param string    $path    The file
368     *
369     * @return bool    True if file is tracked
370     */
371    public function is_tracked($path)
372    {
373        $sql_ary = array(
374            'file_path'        => $path,
375            'storage'        => $this->get_name(),
376        );
377
378        $sql = 'SELECT file_id FROM ' .  $this->storage_table . '
379                WHERE ' . $this->db->sql_build_array('SELECT', $sql_ary);
380        $result = $this->db->sql_query($sql);
381        $row = $this->db->sql_fetchrow($result);
382        $this->db->sql_freeresult($result);
383
384        return ($row) ? true : false;
385    }
386
387    /**
388     * Rename tracked file
389     *
390     * @param string    $path_orig    The original file/direcotry
391     * @param string    $path_dest    The target file/directory
392     */
393    protected function track_rename($path_orig, $path_dest)
394    {
395        $sql = 'UPDATE ' . $this->storage_table . "
396            SET file_path = '" . $this->db->sql_escape($path_dest) . "'
397            WHERE file_path = '" . $this->db->sql_escape($path_orig) . "'
398                AND storage = '" . $this->db->sql_escape($this->get_name()) . "'";
399        $this->db->sql_query($sql);
400    }
401
402    /**
403     * Get file info
404     *
405     * @param string    $path    The file
406     *
407     * @return \phpbb\storage\file_info    Returns file_info object
408     * @throws storage_exception    When the adapter doesn't implement the method
409     *                                                    When the file doesn't exist
410     *
411     */
412    public function file_info($path)
413    {
414        if (!$this->exists($path))
415        {
416            throw new storage_exception('STORAGE_FILE_NO_EXIST', $path);
417        }
418
419        return new file_info($this->get_adapter(), $path);
420    }
421
422    /**
423     * Get direct link
424     *
425     * @param string    $path    The file
426     *
427     * @return string    Returns link.
428     *
429     */
430    public function get_link($path)
431    {
432        return $this->get_adapter()->get_link($path);
433    }
434
435    /**
436     * Get total storage size
437     *
438     * @return int    Size in bytes
439     */
440    public function get_size()
441    {
442        $total_size = $this->cache->get('_storage_' . $this->get_name() . '_totalsize');
443
444        if ($total_size === false)
445        {
446            $sql = 'SELECT SUM(filesize) AS totalsize
447                FROM ' .  $this->storage_table . "
448                WHERE storage = '" . $this->db->sql_escape($this->get_name()) . "'";
449            $result = $this->db->sql_query($sql);
450
451            $total_size = (int) $this->db->sql_fetchfield('totalsize');
452            $this->cache->put('_storage_' . $this->get_name() . '_totalsize', $total_size);
453
454            $this->db->sql_freeresult($result);
455        }
456
457        return (int) $total_size;
458    }
459
460    /**
461     * Get number of storage files
462     *
463     * @return int    Number of files
464     */
465    public function get_num_files()
466    {
467        $number_files = $this->cache->get('_storage_' . $this->get_name() . '_numfiles');
468
469        if ($number_files === false)
470        {
471            $sql = 'SELECT COUNT(file_id) AS numfiles
472                FROM ' .  $this->storage_table . "
473                WHERE storage = '" . $this->db->sql_escape($this->get_name()) . "'";
474            $result = $this->db->sql_query($sql);
475
476            $number_files = (int) $this->db->sql_fetchfield('numfiles');
477            $this->cache->put('_storage_' . $this->get_name() . '_numfiles', $number_files);
478
479            $this->db->sql_freeresult($result);
480        }
481
482        return (int) $number_files;
483    }
484
485    /**
486     * Get space available in bytes
487     *
488     * @return float    Returns available space
489     * @throws storage_exception        When unable to retrieve available storage space
490     *
491     */
492    public function free_space()
493    {
494        return $this->get_adapter()->free_space();
495    }
496}