PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 3.1.2
WP STAGING – WordPress Backup, Restore, Migration & Clone v3.1.2
4.9.3 4.9.2 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
269 lines
1 <?php
2
3 namespace WPStaging\Framework\Queue;
4
5 use Error;
6 use Exception;
7 use WPStaging\Core\Utils\Logger;
8 use WPStaging\Framework\Adapter\Directory;
9 use WPStaging\Framework\Filesystem\FileObject;
10 use WPStaging\Framework\Filesystem\Filesystem;
11
12 use function WPStaging\functions\debug_log;
13
14 class FileSeekableQueue implements SeekableQueueInterface, \SeekableIterator
15 {
16 /** @var string The string identifier of this task */
17 protected $taskName;
18
19 /** @var FileObject The file resource that persists this queue */
20 protected $handle;
21
22 /** @var \Generator */
23 protected $fileGenerator;
24
25 /** @var Directory */
26 protected $directory;
27
28 /** @var Filesystem */
29 protected $filesystem;
30
31 /** @var int */
32 protected $offsetBefore;
33
34 /** @var bool */
35 protected $needsUnlock = false;
36
37 /** @var bool Whether the Queue is in write-only mode. */
38 protected $isWriteOnly;
39
40 public function __construct(Directory $directory, Filesystem $filesystem)
41 {
42 $this->directory = $directory;
43 $this->filesystem = $filesystem;
44 }
45
46 public function __destruct()
47 {
48 $this->shutdown();
49 }
50
51 /**
52 * @param $taskName
53 * @param string $queueMode Either opens the Queue for read and write, or optimized to write-only.
54 */
55 public function setup($taskName, $queueMode = SeekableQueueInterface::MODE_READ_WRITE)
56 {
57 $this->taskName = $taskName;
58
59 $path = "{$this->directory->getCacheDirectory()}$taskName.cache";
60
61 $this->filesystem->mkdir(dirname($path), true);
62
63 if (!file_exists($path) && !touch($path)) {
64 debug_log("Check if there is enough free space and the file permissions are 644 or 755. Could not create file: $path");
65 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));
66 }
67
68 // Developer exception
69 if ($queueMode !== SeekableQueueInterface::MODE_WRITE && $queueMode !== SeekableQueueInterface::MODE_READ_WRITE) {
70 throw new \BadMethodCallException();
71 }
72
73 $this->handle = new FileObject($path, $queueMode);
74 $this->handle->setFlags(FileObject::DROP_NEW_LINE);
75 $this->fileGenerator = $this->initializeGenerator();
76
77 $this->isWriteOnly = $queueMode === SeekableQueueInterface::MODE_WRITE;
78
79 if ($this->isWriteOnly) {
80 $waitedTimes = 0;
81 do {
82 $wouldBlock = false;
83
84 /*
85 * Windows does not support LOCK_NB (Advisory locking), so we read from the return of flock.
86 * Unix supports LOCK_NB, so we read from the second parameter of flock.
87 */
88 $locked = $this->handle->flock(LOCK_EX | LOCK_NB, $wouldBlock) || (bool)!$wouldBlock;
89
90 if (!$locked) {
91 usleep(250000); // 0.25s
92 $waitedTimes++;
93 if ($waitedTimes > 5) {
94 throw new \RuntimeException(sprintf(esc_html__('Could not acquire exclusive lock for writing to Queue file: %s.task', 'wp-staging'), $this->taskName));
95 }
96 }
97 } while (!$locked);
98
99 $this->needsUnlock = true;
100 }
101 }
102
103 protected function initializeGenerator()
104 {
105 while ($this->handle->valid()) {
106 $this->offsetBefore = $this->handle->ftell();
107 yield $this->handle->readAndMoveNext();
108 }
109 }
110
111 #[\ReturnTypeWillChange]
112 public function current()
113 {
114 return $this->fileGenerator->current();
115 }
116
117 #[\ReturnTypeWillChange]
118 public function next()
119 {
120 $this->fileGenerator->next();
121 }
122
123 #[\ReturnTypeWillChange]
124 public function key()
125 {
126 return $this->fileGenerator->key();
127 }
128
129 #[\ReturnTypeWillChange]
130 public function valid()
131 {
132 return $this->fileGenerator->valid();
133 }
134
135 #[\ReturnTypeWillChange]
136 public function rewind()
137 {
138 $this->handle->fseek(0);
139 }
140
141 #[\ReturnTypeWillChange]
142 public function seek($offset)
143 {
144 $this->handle->fseek($offset);
145 }
146
147 public function isFinished()
148 {
149 return $this->handle->eof();
150 }
151
152 /**
153 * @param $dequeue
154 * @return mixed|void
155 */
156 public function retry($dequeue = true)
157 {
158 $this->seek($this->offsetBefore);
159
160 if ($dequeue) {
161 return $this->dequeue();
162 }
163 }
164
165 /**
166 * @param $data
167 * @return false|int
168 */
169 public function enqueue($data)
170 {
171 // Early bail: Write-only optimization
172 if ($this->isWriteOnly) {
173 $this->handle->fwrite(trim($data) . PHP_EOL);
174
175 return $this->handle->ftell();
176 }
177
178 $currentOffset = $this->handle->ftell();
179
180 $this->handle->fseek(0, SEEK_END);
181 $this->handle->flock(LOCK_EX);
182 $this->handle->fwrite(trim($data) . PHP_EOL);
183 $this->handle->flock(LOCK_UN);
184
185 $offsetEndOfQueue = $this->handle->ftell();
186 $this->handle->fseek($currentOffset);
187
188 return $offsetEndOfQueue;
189 }
190
191 /**
192 * @return mixed
193 */
194 public function dequeue()
195 {
196 if ($this->isWriteOnly) {
197 throw new \BadMethodCallException('Trying to read from read-only Queue');
198 }
199
200 $first = is_null($this->offsetBefore);
201
202 if (!$first) {
203 $this->next();
204 }
205
206 return $this->current();
207 }
208
209 /**
210 * @param array $data
211 * @return false|int
212 */
213 public function enqueueMany(array $data = [])
214 {
215 foreach ($data as $item) {
216 if (is_scalar($item)) {
217 $this->enqueue((string)$item);
218 }
219 }
220
221 return $this->handle->ftell();
222 }
223
224 public function reset()
225 {
226 $this->handle->ftruncate(0);
227 }
228
229 /**
230 * @return false|int
231 */
232 public function getOffset()
233 {
234 if (!isset($this->handle) || !$this->handle instanceof FileObject) {
235 return false;
236 }
237
238 return $this->handle->ftell();
239 }
240
241 /**
242 * @return void
243 */
244 public function shutdown()
245 {
246 if ($this->needsUnlock && $this->handle instanceof FileObject) {
247 $this->unlockObject();
248 return;
249 }
250 }
251
252 protected function unlockObject()
253 {
254 try {
255 $this->handle->flock(LOCK_UN);
256 } catch (Exception $e) {
257 $message = $e->getMessage();
258 if ($message !== 'Object not initialized') {
259 debug_log("Unable to unlock handle " . $this->taskName . '.task : ' . $message, Logger::TYPE_DEBUG);
260 }
261 } catch (Error $e) {
262 $message = $e->getMessage();
263 if ($message !== 'Object not initialized') {
264 debug_log("Unable to unlock handle " . $this->taskName . '.task : ' . $message, Logger::TYPE_DEBUG);
265 }
266 }
267 }
268 }
269