PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 3.8.0
WP STAGING – WordPress Backup, Restore, Migration & Clone v3.8.0
4.9.1 4.9.0 4.8.1 trunk 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 3.1.0 3.1.1 3.1.2 3.1.3 3.1.4 3.10.0 3.2.0 3.3.1 3.3.2 3.3.3 3.4.1 3.4.3 3.5.0 3.6.0 3.7.1 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.8.6 3.8.7 3.9.0 3.9.1 3.9.2 3.9.3 3.9.4 4.0.0 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0 4.2.1 4.3.0 4.3.1 4.3.2 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.7.2 4.7.3 4.8.0
wp-staging / Framework / Queue / FileSeekableQueue.php
wp-staging / Framework / Queue Last commit date
Storage 2 years ago FileSeekableQueue.php 2 years ago FinishedQueueException.php 5 years ago Queue.php 2 years ago QueueInterface.php 4 years ago SeekableQueueInterface.php 4 years ago
FileSeekableQueue.php
316 lines
1 <?php
2
3 namespace WPStaging\Framework\Queue;
4
5 use Error;
6 use Exception;
7 use RuntimeException;
8 use WPStaging\Core\Utils\Logger;
9 use WPStaging\Framework\Adapter\Directory;
10 use WPStaging\Framework\Filesystem\FileObject;
11 use WPStaging\Framework\Filesystem\Filesystem;
12 use WPStaging\Framework\Utils\Cache\Cache;
13
14 use function WPStaging\functions\debug_log;
15
16 class FileSeekableQueue implements SeekableQueueInterface, \SeekableIterator
17 {
18 /** @var string */
19 const FILE_EXTENSION = 'cache.php';
20
21 /** @var string The string identifier of this task */
22 protected $taskName;
23
24 /** @var FileObject The file resource that persists this queue */
25 protected $handle;
26
27 /** @var \Generator */
28 protected $fileGenerator;
29
30 /** @var Directory */
31 protected $directory;
32
33 /** @var Filesystem */
34 protected $filesystem;
35
36 /** @var int */
37 protected $offsetBefore;
38
39 /** @var bool */
40 protected $needsUnlock = false;
41
42 /** @var bool Whether the Queue is in write-only mode. */
43 protected $isWriteOnly;
44
45 public function __construct(Directory $directory, Filesystem $filesystem)
46 {
47 $this->directory = $directory;
48 $this->filesystem = $filesystem;
49 }
50
51 public function __destruct()
52 {
53 $this->shutdown();
54 }
55
56 /**
57 * @param string $taskName
58 * @param string $queueMode Either opens the Queue for read and write, or optimized to write-only.
59 * @return void
60 */
61 public function setup($taskName, $queueMode = SeekableQueueInterface::MODE_READ_WRITE)
62 {
63 $this->taskName = $taskName;
64
65 $extension = self::FILE_EXTENSION;
66 $path = "{$this->directory->getCacheDirectory()}$taskName.$extension";
67
68 $this->filesystem->mkdir(dirname($path), true);
69
70 $isNewQueue = $this->createQueue($path);
71
72 // Developer exception
73 if ($queueMode !== SeekableQueueInterface::MODE_WRITE && $queueMode !== SeekableQueueInterface::MODE_READ_WRITE) {
74 throw new \BadMethodCallException();
75 }
76
77 $this->handle = new FileObject($path, $queueMode);
78 $this->handle->setFlags(FileObject::DROP_NEW_LINE);
79 $this->fileGenerator = $this->initializeGenerator();
80
81 $this->isWriteOnly = $queueMode === SeekableQueueInterface::MODE_WRITE;
82
83 if ($this->isWriteOnly) {
84 $waitedTimes = 0;
85 do {
86 $wouldBlock = false;
87
88 /*
89 * Windows does not support LOCK_NB (Advisory locking), so we read from the return of flock.
90 * Unix supports LOCK_NB, so we read from the second parameter of flock.
91 */
92 $locked = $this->handle->flock(LOCK_EX | LOCK_NB, $wouldBlock) || (bool)!$wouldBlock;
93
94 if (!$locked) {
95 usleep(250000); // 0.25s
96 $waitedTimes++;
97 if ($waitedTimes > 5) {
98 throw new \RuntimeException(sprintf(esc_html__('Could not acquire exclusive lock for writing to Queue file: %s.task', 'wp-staging'), $this->taskName));
99 }
100 }
101 } while (!$locked);
102
103 $this->needsUnlock = true;
104 }
105
106 // Add the PHP header to the queue if it is a new queue
107 if ($isNewQueue) {
108 $this->enqueue(Cache::PHP_HEADER);
109 }
110 }
111
112 /**
113 * @return \Generator
114 */
115 protected function initializeGenerator()
116 {
117 while ($this->handle->valid()) {
118 $this->offsetBefore = $this->handle->ftell();
119 yield $this->handle->readAndMoveNext();
120 }
121 }
122
123 #[\ReturnTypeWillChange]
124 public function current()
125 {
126 return $this->fileGenerator->current();
127 }
128
129 #[\ReturnTypeWillChange]
130 public function next()
131 {
132 $this->fileGenerator->next();
133 }
134
135 #[\ReturnTypeWillChange]
136 public function key()
137 {
138 return $this->fileGenerator->key();
139 }
140
141 #[\ReturnTypeWillChange]
142 public function valid()
143 {
144 return $this->fileGenerator->valid();
145 }
146
147 #[\ReturnTypeWillChange]
148 public function rewind()
149 {
150 $this->handle->fseek(0);
151 }
152
153 #[\ReturnTypeWillChange]
154 public function seek($offset)
155 {
156 $this->handle->fseek($offset);
157 }
158
159 /**
160 * @return bool
161 */
162 public function isFinished(): bool
163 {
164 return $this->handle->eof();
165 }
166
167 /**
168 * @param bool $dequeue
169 * @return string|void
170 */
171 public function retry($dequeue = true)
172 {
173 $this->seek($this->offsetBefore);
174
175 if ($dequeue) {
176 return $this->dequeue();
177 }
178 }
179
180 /**
181 * @param string $data
182 * @return false|int
183 */
184 public function enqueue($data)
185 {
186 $trimmedData = trim($data);
187 // Early bail: Write-only optimization
188 if ($this->isWriteOnly) {
189 $this->handle->fwrite($trimmedData . PHP_EOL);
190
191 return $this->handle->ftell();
192 }
193
194 $currentOffset = $this->handle->ftell();
195
196 $this->handle->fseek(0, SEEK_END);
197 $this->handle->flock(LOCK_EX);
198 $this->handle->fwrite($trimmedData . PHP_EOL);
199 $this->handle->flock(LOCK_UN);
200
201 $offsetEndOfQueue = $this->handle->ftell();
202 $this->handle->fseek($currentOffset);
203
204 return $offsetEndOfQueue;
205 }
206
207 /**
208 * @return string|false
209 */
210 public function dequeue()
211 {
212 if ($this->isWriteOnly) {
213 throw new \BadMethodCallException('Trying to read from read-only Queue');
214 }
215
216 $first = is_null($this->offsetBefore);
217
218 if (!$first) {
219 $this->next();
220 }
221
222 $current = $this->current();
223 if ($current !== rtrim(Cache::PHP_HEADER)) {
224 return $current;
225 }
226
227 $this->next();
228 return $this->current();
229 }
230
231 /**
232 * @param array $data
233 * @return false|int
234 */
235 public function enqueueMany(array $data = [])
236 {
237 foreach ($data as $item) {
238 if (is_scalar($item)) {
239 $this->enqueue((string)$item);
240 }
241 }
242
243 return $this->handle->ftell();
244 }
245
246 /**
247 * @return void
248 */
249 public function reset()
250 {
251 $this->handle->ftruncate(0);
252 }
253
254 /**
255 * @return false|int
256 */
257 public function getOffset()
258 {
259 if (!isset($this->handle) || !$this->handle instanceof FileObject) {
260 return false;
261 }
262
263 return $this->handle->ftell();
264 }
265
266 /**
267 * @return void
268 */
269 public function shutdown()
270 {
271 if ($this->needsUnlock && $this->handle instanceof FileObject) {
272 $this->unlockObject();
273 return;
274 }
275 }
276
277 /**
278 * @return void
279 */
280 protected function unlockObject()
281 {
282 try {
283 $this->handle->flock(LOCK_UN);
284 } catch (Exception $e) {
285 $message = $e->getMessage();
286 if ($message !== 'Object not initialized') {
287 debug_log("Unable to unlock handle " . $this->taskName . '.task : ' . $message, Logger::TYPE_DEBUG);
288 }
289 } catch (Error $e) {
290 $message = $e->getMessage();
291 if ($message !== 'Object not initialized') {
292 debug_log("Unable to unlock handle " . $this->taskName . '.task : ' . $message, Logger::TYPE_DEBUG);
293 }
294 }
295 }
296
297 /**
298 * @param string $path
299 * @return bool True if the file was created, false if it already existed
300 * @throws RuntimeException when file cannot be created
301 */
302 protected function createQueue(string $path): bool
303 {
304 if (file_exists($path)) {
305 return false;
306 }
307
308 if (!touch($path)) {
309 debug_log("Check if there is enough free space and the file permissions are 644 or 755. Could not create file: $path");
310 throw new RuntimeException(sprintf(esc_html__("Check if there is enough free space and the file permissions are 644 or 755. Could not create file: %s", 'wp-staging'), $path));
311 }
312
313 return true;
314 }
315 }
316