class-site-size.php
7.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
<?php
namespace HM\BackUpWordPress;
use HM\Backdrop\Task;
use Symfony\Component\Finder\Finder;
/**
* Site Size class
*
* Use to calculate the total or partial size of the sites database and files.
*/
class Site_Size {
private $size = 0;
private $type = '';
private $excludes = array();
/**
* Constructor
*
* Set up some initial conditions including whether we want to calculate the
* size of the database, files or both and whether to exclude any files from
* the file size calculation.
*
* @param string $type Whether to calculate the size of the database, files
* or both. Should be one of 'file', 'database' or 'complete'
* @param array $excludes An array of exclude rules
*/
public function __construct( $type = 'complete', Excludes $excludes = null ) {
$this->type = $type;
$this->excludes = $excludes;
}
/**
* Calculate the size total size of the database + files.
*
* Doesn't account for any compression that would be gained by zipping.
*
* @return string
*/
public function get_site_size() {
if ( $this->size ) {
return $this->size;
}
$size = 0;
// Include database size except for file only schedule.
if ( 'file' !== $this->type ) {
$size = (int) get_transient( 'hmbkp_database_size' );
if ( ! $size ) {
global $wpdb;
$tables = $wpdb->get_results( 'SHOW TABLE STATUS FROM `' . DB_NAME . '`', ARRAY_A );
foreach ( $tables as $table ) {
$size += (float) $table['Data_length'];
}
set_transient( 'hmbkp_database_size', $size, WEEK_IN_SECONDS );
}
}
// Include total size of dirs/files except for database only schedule.
if ( 'database' !== $this->type ) {
$root = new \SplFileInfo( Path::get_root() );
$size += $this->filesize( $root );
}
$this->size = $size;
return $size;
}
/**
* Get the site size formatted
*
* @see size_format
*
* @return string
*/
public function get_formatted_site_size() {
return size_format( $this->get_site_size() );
}
/**
* Whether the total filesize is being calculated
*
* @return bool
*/
public static function is_site_size_being_calculated() {
return false !== get_transient( 'hmbkp_directory_filesizes_running' );
}
/**
* Whether the total filesize is cached
*
* @return bool
*/
public function is_site_size_cached() {
return (bool) $this->get_cached_filesizes();
}
/**
* Recursively scans a directory to calculate the total filesize
*
* Locks should be set by the caller with `set_transient( 'hmbkp_directory_filesizes_running', true, HOUR_IN_SECONDS );`
*
* @return array $directory_sizes An array of directory paths => filesize sum of all files in directory
*/
public function recursive_filesize_scanner() {
/**
* Raise the `memory_limit` and `max_execution time`
*
* Respects the WP_MAX_MEMORY_LIMIT Constant and the `admin_memory_limit`
* filter.
*/
@ini_set( 'memory_limit', apply_filters( 'admin_memory_limit', WP_MAX_MEMORY_LIMIT ) );
@set_time_limit( 0 );
// Use the cached array directory sizes if available
$directory_sizes = $this->get_cached_filesizes();
// If we do have it in cache then let's use it and also clear the lock
if ( is_array( $directory_sizes ) ) {
delete_transient( 'hmbkp_directory_filesizes_running' );
return $directory_sizes;
}
// If we don't have it cached then we'll need to re-calculate
$finder = new Finder();
$finder->followLinks();
$finder->ignoreDotFiles( false );
$finder->ignoreUnreadableDirs( true );
$files = $finder->in( Path::get_root() );
foreach ( $files as $file ) {
if ( $file->isReadable() ) {
$directory_sizes[ wp_normalize_path( $file->getRealpath() ) ] = $file->getSize();
} else {
$directory_sizes[ wp_normalize_path( $file->getRealpath() ) ] = 0;
}
}
file_put_contents( PATH::get_path() . '/.files', gzcompress( json_encode( $directory_sizes ) ) );
// Remove the lock
delete_transient( 'hmbkp_directory_filesizes_running' );
return $directory_sizes;
}
/**
* Get the total filesize for a given file or directory
*
* If $file is a file then just return the result of `filesize()`.
* If $file is a directory then recursively calculate the size.
*
* @param \SplFileInfo $file The file or directory you want to know the size of
*
* @return int The total filesize of the file or directory
*/
public function filesize( \SplFileInfo $file ) {
// Skip missing or unreadable files
if ( ! file_exists( $file->getPathname() ) || ! $file->getRealpath() || ! $file->isReadable() ) {
return 0;
}
// If it's a file then just pass back the filesize
if ( $file->isFile() ) {
return $file->getSize();
}
// If it's a directory then pull it from the cached filesize array
if ( $file->isDir() ) {
return $this->directory_filesize( $file );
}
}
public function directory_filesize( \SplFileInfo $file ) {
// For performance reasons we cache the root.
if ( $file->getRealPath() === PATH::get_root() && $this->excludes ) {
$directory_sizes = get_transient( 'hmbkp_root_size' );
if ( $directory_sizes ) {
return $directory_sizes;
}
}
// If we haven't calculated the site size yet then kick it off in a thread
$directory_sizes = $this->get_cached_filesizes();
if ( ! is_array( $directory_sizes ) ) {
$this->rebuild_directory_filesizes();
// Intentionally return null so the caller can tell that the size is being calculated
return null;
}
/*
* Ensure we only include files in the current path, the filepaths are stored in keys
* so we need to flip for use with preg_grep.
*/
$directory_sizes = array_flip( preg_grep( '(' . wp_normalize_path( $file->getRealPath() ) . ')', array_flip( $directory_sizes ) ) );
if ( $this->excludes ) {
$excludes = implode( '|', $this->excludes->get_excludes_for_regex() );
if ( $excludes ) {
// Use PREG_GREP_INVERT to remove any filepaths which match an exclude rule
$directory_sizes = array_flip( preg_grep( '(' . $excludes . ')', array_flip( $directory_sizes ), PREG_GREP_INVERT ) );
}
}
$directory_sizes = absint( array_sum( $directory_sizes ) );
// For performance reasons we cache the root.
if ( $file->getRealPath() === PATH::get_root() && $this->excludes ) {
set_transient( 'hmbkp_root_size', $directory_sizes, DAY_IN_SECONDS );
}
// Directory size is now just a sum of all files across all sub directories.
return $directory_sizes;
}
public function rebuild_directory_filesizes() {
if ( $this->is_site_size_being_calculated() ) {
return false;
}
// Mark the filesize as being calculated
set_transient( 'hmbkp_directory_filesizes_running', true, HOUR_IN_SECONDS );
// Schedule a Backdrop task to trigger a recalculation
$task = new Task( array( $this, 'recursive_filesize_scanner' ) );
$task->schedule();
}
public function get_cached_filesizes( $max_age = WEEK_IN_SECONDS ) {
$cache = PATH::get_path() . '/.files';
$files = false;
if ( file_exists( $cache ) ) {
// If the file is old then regenerate it
if ( ( time() - filemtime( $cache ) ) <= $max_age ) {
$files = json_decode( gzuncompress( file_get_contents( $cache ) ), 'ARRAY_A' );
}
}
return $files;
}
}