PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / Archive / Gzip.php
backup / src / JetBackup / Archive Last commit date
Data 1 year ago File 1 day ago Header 1 year ago Scan 9 months ago .htaccess 1 year ago Archive.php 1 day ago Gzip.php 4 months ago index.html 1 year ago web.config 1 year ago
Gzip.php
212 lines
1 <?php
2
3 namespace JetBackup\Archive;
4
5 use JetBackup\DirIterator\DirIteratorFile;
6 use JetBackup\Exception\GzipException;
7
8 if (!defined( '__JETBACKUP__')) die('Direct access is not allowed');
9
10 /**
11 * Class for compressing files using gzip.
12 */
13 class Gzip {
14
15 const MAX_RETRIES = 3;
16 const RETRY_DELAY_MS = 100;
17 const DEFAULT_COMPRESS_CHUNK_SIZE = 10485760; // 10MB
18 const DEFAULT_DECOMPRESS_CHUNK_SIZE = 1048576; // 1MB
19 const DEFAULT_COMPRESSION_LEVEL = -1;
20
21 private function __construct() {}
22
23 /**
24 * @throws GzipException
25 */
26 private static function _getInfo($file) {
27 $info = new \stdClass();
28 if(file_exists($file)) {
29 $info = json_decode(file_get_contents($file));
30 if($info === false) throw new GzipException("Failed fetching compress information");
31 }
32 return $info;
33 }
34
35 /**
36 * @param $file
37 * @param $data
38 *
39 * @return void
40 * @throws GzipException
41 */
42 private static function _putInfo($file, $data)
43 {
44 $tempFile = $file . '.tmp';
45
46 $jsonData = json_encode($data);
47 if ($jsonData === false) throw new GzipException("Failed to encode compress information: " . json_last_error_msg());
48
49 $maxRetries = self::MAX_RETRIES;
50 $retryDelayMs = self::RETRY_DELAY_MS;
51 $lastErrorWrite = null;
52 $bytesWritten = false;
53
54 for ($i = 0; $i < $maxRetries; $i++) {
55
56 $lastErrorWrite = null;
57
58 set_error_handler(function ($severity, $message, $errFile, $errLine) use (&$lastErrorWrite) {
59 $lastErrorWrite = $message . " in {$errFile}:{$errLine}";
60 return true;
61 });
62
63 $bytesWritten = @file_put_contents($tempFile, $jsonData);
64 restore_error_handler();
65
66 if ($bytesWritten !== false) break;
67 usleep($retryDelayMs * 1000); // retry
68 }
69
70 if ($bytesWritten === false) {
71 $extra = $lastErrorWrite ? " ({$lastErrorWrite})" : '';
72 throw new GzipException("Failed to write compress information to temporary file '{$tempFile}' after {$maxRetries} attempts{$extra}");
73 }
74
75 $lastErrorRename = null;
76
77 for ($i = 0; $i < $maxRetries; $i++) {
78
79 $lastErrorRename = null;
80
81 set_error_handler(function ($severity, $message, $errFile, $errLine) use (&$lastErrorRename) {
82 $lastErrorRename = $message . " in {$errFile}:{$errLine}";
83 return true;
84 });
85
86 $renamed = @rename($tempFile, $file);
87 restore_error_handler();
88
89 if ($renamed === true) return;
90 usleep($retryDelayMs * 1000);
91 }
92
93 $extra = $lastErrorRename ? " ({$lastErrorRename})" : '';
94 throw new GzipException("Failed to atomically write compress information to '{$file}' after {$maxRetries} attempts{$extra}");
95 }
96
97
98
99 /**
100 * @throws GzipException
101 */
102 public static function compress($file, $chunkSize=self::DEFAULT_COMPRESS_CHUNK_SIZE, $compressionLevel=self::DEFAULT_COMPRESSION_LEVEL, ?callable $callback=null) {
103 if(!file_exists($file) || !is_file($file)) throw new GzipException("Source file not found");
104
105 $target = $file . '.gz';
106
107 $info_file = $target . '.compress.info';
108 $info = self::_getInfo($info_file);
109
110 $fd = fopen($file, 'r');
111 $gzfd = fopen($target, 'ab');
112
113 $read = $write = 0;
114 if(isset($info->fdpos) && $info->fdpos) {
115 $read = $info->fdpos;
116 fseek($fd, $info->fdpos);
117 }
118 if(isset($info->fdgzpos) && $info->fdgzpos) {
119 $write = $info->fdgzpos;
120 fseek($gzfd, $info->fdgzpos);
121 }
122
123 $fileSize = DirIteratorFile::safe_filesize($file);
124
125 while(!feof($fd)) {
126 // Call callback BEFORE the expensive gzencode operation to check execution time
127 // This allows graceful exit before starting work that might exceed time limits
128 if($callback) $callback($read, $fileSize);
129
130 $chunk = fread($fd, $chunkSize);
131 $bytesRead = strlen($chunk);
132
133 if($bytesRead === 0) break;
134
135 $compressed = gzencode($chunk, $compressionLevel);
136 $bytesWritten = fwrite($gzfd, $compressed);
137
138 $read += $bytesRead;
139 $write += $bytesWritten;
140
141 self::_putInfo($info_file, [
142 'fdpos' => $read,
143 'fdgzpos' => $write,
144 ]);
145 }
146
147 if(feof($fd)) {
148 @unlink($file);
149 @unlink($info_file);
150 }
151
152 fclose($fd);
153 fclose($gzfd);
154 }
155
156 /**
157 * @throws GzipException
158 */
159 public static function decompress($file, ?callable $callback=null, $chunkSize=self::DEFAULT_DECOMPRESS_CHUNK_SIZE) {
160 if(!file_exists($file) || !is_file($file)) throw new GzipException("Source file not found");
161
162 $info_file = $file . '.decompress.info';
163 $info = self::_getInfo($info_file);
164
165 // remove .gz suffix
166 $target = substr($file, 0, -3);
167
168 $fd = fopen($target, 'a');
169 $gzfd = gzopen($file, 'rb');
170
171 $read = $write = 0;
172 if(isset($info->fdpos) && $info->fdpos) {
173 $write = $info->fdpos;
174 fseek($fd, $info->fdpos);
175 }
176
177 if(isset($info->fdgzpos) && $info->fdgzpos) {
178 $read = $info->fdgzpos;
179 gzseek($gzfd, $info->fdgzpos);
180 }
181
182 $estimatedTotalSize = (int) (DirIteratorFile::safe_filesize($file) * 3); // estimating X3 compression ratio
183
184 while(!gzeof($gzfd)) {
185 // Call callback BEFORE the expensive gzread operation to check execution time
186 if($callback) $callback('Gzip', 'Decompressing', $estimatedTotalSize, $read);
187
188 $chunk = gzread($gzfd, $chunkSize);
189 $bytesRead = strlen($chunk);
190
191 if($bytesRead === 0) break;
192
193 $bytesWritten = fwrite($fd, $chunk);
194
195 $read += $bytesRead;
196 $write += $bytesWritten;
197
198 self::_putInfo($info_file, [
199 'fdpos' => $write,
200 'fdgzpos' => $read,
201 ]);
202 }
203
204 if(gzeof($gzfd)) {
205 @unlink($file);
206 @unlink($info_file);
207 }
208
209 fclose($fd);
210 gzclose($gzfd);
211 }
212 }