Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
77.08% |
37 / 48 |
|
63.64% |
7 / 11 |
CRAP | |
0.00% |
0 / 1 |
FSFile | |
77.08% |
37 / 48 |
|
63.64% |
7 / 11 |
23.34 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getPath | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
exists | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSize | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getTimestamp | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 | |||
getProps | |
91.67% |
11 / 12 |
|
0.00% |
0 / 1 |
4.01 | |||
placeholderProps | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
getSha1Base36 | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
4 | |||
extensionFromPath | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getPropsFromPath | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getSha1Base36FromPath | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * Non-directory file on the file system. |
4 | * |
5 | * This program is free software; you can redistribute it and/or modify |
6 | * it under the terms of the GNU General Public License as published by |
7 | * the Free Software Foundation; either version 2 of the License, or |
8 | * (at your option) any later version. |
9 | * |
10 | * This program is distributed in the hope that it will be useful, |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | * |
15 | * You should have received a copy of the GNU General Public License along |
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
18 | * http://www.gnu.org/copyleft/gpl.html |
19 | * |
20 | * @file |
21 | * @ingroup FileBackend |
22 | */ |
23 | |
24 | use Wikimedia\AtEase\AtEase; |
25 | use Wikimedia\Timestamp\ConvertibleTimestamp; |
26 | |
27 | /** |
28 | * Class representing a non-directory file on the file system |
29 | * |
30 | * @ingroup FileBackend |
31 | */ |
32 | class FSFile { |
33 | /** @var string Path to file */ |
34 | protected $path; |
35 | |
36 | /** @var string File SHA-1 in base 36 */ |
37 | protected $sha1Base36; |
38 | |
39 | /** |
40 | * Sets up the file object |
41 | * |
42 | * @param string $path Path to temporary file on local disk |
43 | */ |
44 | public function __construct( $path ) { |
45 | $this->path = $path; |
46 | } |
47 | |
48 | /** |
49 | * Returns the file system path |
50 | * |
51 | * @return string |
52 | */ |
53 | public function getPath() { |
54 | return $this->path; |
55 | } |
56 | |
57 | /** |
58 | * Checks if the file exists |
59 | * |
60 | * @return bool |
61 | */ |
62 | public function exists() { |
63 | return is_file( $this->path ); |
64 | } |
65 | |
66 | /** |
67 | * Get the file size in bytes |
68 | * |
69 | * @return int|bool |
70 | */ |
71 | public function getSize() { |
72 | AtEase::suppressWarnings(); |
73 | $size = filesize( $this->path ); |
74 | AtEase::restoreWarnings(); |
75 | |
76 | return $size; |
77 | } |
78 | |
79 | /** |
80 | * Get the file's last-modified timestamp |
81 | * |
82 | * @return string|bool TS_MW timestamp or false on failure |
83 | */ |
84 | public function getTimestamp() { |
85 | AtEase::suppressWarnings(); |
86 | $timestamp = filemtime( $this->path ); |
87 | AtEase::restoreWarnings(); |
88 | if ( $timestamp !== false ) { |
89 | $timestamp = ConvertibleTimestamp::convert( TS_MW, $timestamp ); |
90 | } |
91 | |
92 | return $timestamp; |
93 | } |
94 | |
95 | /** |
96 | * Get an associative array containing information about |
97 | * a file with the given storage path. |
98 | * |
99 | * Resulting array fields include: |
100 | * - fileExists |
101 | * - size (filesize in bytes) |
102 | * - mime (as major/minor) |
103 | * - file-mime (as major/minor) |
104 | * - sha1 (in base 36) |
105 | * - major_mime |
106 | * - minor_mime |
107 | * |
108 | * @param string|bool $ext The file extension, or true to extract it from the filename. |
109 | * Set it to false to ignore the extension. Currently unused. |
110 | * @return array |
111 | */ |
112 | public function getProps( $ext = true ) { |
113 | $info = self::placeholderProps(); |
114 | $info['fileExists'] = $this->exists(); |
115 | |
116 | if ( $info['fileExists'] ) { |
117 | $info['size'] = $this->getSize(); // bytes |
118 | $info['sha1'] = $this->getSha1Base36(); |
119 | |
120 | $mime = mime_content_type( $this->path ); |
121 | # MIME type according to file contents |
122 | $info['file-mime'] = ( $mime === false ) ? 'unknown/unknown' : $mime; |
123 | # logical MIME type |
124 | $info['mime'] = $mime; |
125 | |
126 | if ( strpos( $mime, '/' ) !== false ) { |
127 | [ $info['major_mime'], $info['minor_mime'] ] = explode( '/', $mime, 2 ); |
128 | } else { |
129 | [ $info['major_mime'], $info['minor_mime'] ] = [ $mime, 'unknown' ]; |
130 | } |
131 | } |
132 | |
133 | return $info; |
134 | } |
135 | |
136 | /** |
137 | * Placeholder file properties to use for files that don't exist |
138 | * |
139 | * Resulting array fields include: |
140 | * - fileExists |
141 | * - size (filesize in bytes) |
142 | * - mime (as major/minor) |
143 | * - file-mime (as major/minor) |
144 | * - sha1 (in base 36) |
145 | * - major_mime |
146 | * - minor_mime |
147 | * |
148 | * @return array |
149 | */ |
150 | public static function placeholderProps() { |
151 | $info = []; |
152 | $info['fileExists'] = false; |
153 | $info['size'] = 0; |
154 | $info['file-mime'] = null; |
155 | $info['major_mime'] = null; |
156 | $info['minor_mime'] = null; |
157 | $info['mime'] = null; |
158 | $info['sha1'] = ''; |
159 | |
160 | return $info; |
161 | } |
162 | |
163 | /** |
164 | * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case |
165 | * encoding, zero padded to 31 digits. |
166 | * |
167 | * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36 |
168 | * fairly neatly. |
169 | * |
170 | * @param bool $recache |
171 | * @return bool|string False on failure |
172 | */ |
173 | public function getSha1Base36( $recache = false ) { |
174 | if ( $this->sha1Base36 !== null && !$recache ) { |
175 | return $this->sha1Base36; |
176 | } |
177 | |
178 | AtEase::suppressWarnings(); |
179 | $this->sha1Base36 = sha1_file( $this->path ); |
180 | AtEase::restoreWarnings(); |
181 | |
182 | if ( $this->sha1Base36 !== false ) { |
183 | $this->sha1Base36 = Wikimedia\base_convert( $this->sha1Base36, 16, 36, 31 ); |
184 | } |
185 | |
186 | return $this->sha1Base36; |
187 | } |
188 | |
189 | /** |
190 | * Get the final file extension from a file system path |
191 | * |
192 | * @param string $path |
193 | * @return string |
194 | */ |
195 | public static function extensionFromPath( $path ) { |
196 | $i = strrpos( $path, '.' ); |
197 | |
198 | return strtolower( $i ? substr( $path, $i + 1 ) : '' ); |
199 | } |
200 | |
201 | /** |
202 | * Get an associative array containing information about a file in the local filesystem. |
203 | * |
204 | * @param string $path Absolute local filesystem path |
205 | * @param string|bool $ext The file extension, or true to extract it from the filename. |
206 | * Set it to false to ignore the extension. |
207 | * @return array |
208 | */ |
209 | public static function getPropsFromPath( $path, $ext = true ) { |
210 | $fsFile = new self( $path ); |
211 | |
212 | return $fsFile->getProps( $ext ); |
213 | } |
214 | |
215 | /** |
216 | * Get a SHA-1 hash of a file in the local filesystem, in base-36 lower case |
217 | * encoding, zero padded to 31 digits. |
218 | * |
219 | * 160 log 2 / log 36 = 30.95, so the 160-bit hash fills 31 digits in base 36 |
220 | * fairly neatly. |
221 | * |
222 | * @param string $path |
223 | * @return false|string False on failure |
224 | */ |
225 | public static function getSha1Base36FromPath( $path ) { |
226 | $fsFile = new self( $path ); |
227 | |
228 | return $fsFile->getSha1Base36(); |
229 | } |
230 | } |