PluginProbe ʕ •ᴥ•ʔ
WP STAGING – WordPress Backup, Restore, Migration & Clone / 3.0.1
WP STAGING – WordPress Backup, Restore, Migration & Clone v3.0.1
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 3 years ago FileSeekableQueue.php 3 years ago FinishedQueueException.php 5 years ago Queue.php 3 years ago QueueInterface.php 4 years ago SeekableQueueInterface.php 4 years ago
FileSeekableQueue.php
268 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 $this->handle = new FileObject($path, $queueMode);
73 $this->handle->setFlags(FileObject::DROP_NEW_LINE);
74 $this->fileGenerator = $this->initializeGenerator();
75
76 $this->isWriteOnly = $queueMode === SeekableQueueInterface::MODE_WRITE;
77
78 if ($this->isWriteOnly) {
79 $waitedTimes = 0;
80 do {
81 $wouldBlock = false;
82
83 /*
84 * Windows does not support LOCK_NB (Advisory locking), so we read from the return of flock.
85 * Unix supports LOCK_NB, so we read from the second parameter of flock.
86 */
87 $locked = $this->handle->flock(LOCK_EX | LOCK_NB, $wouldBlock) || (bool)!$wouldBlock;
88
89 if (!$locked) {
90 usleep(250000); // 0.25s
91 $waitedTimes++;
92 if ($waitedTimes > 5) {
93 throw new \RuntimeException(sprintf(esc_html__('Could not acquire exclusive lock for writing to Queue file: %s.task', 'wp-staging'), $this->taskName));
94 }
95 }
96 } while (!$locked);
97
98 $this->needsUnlock = true;
99 }
100 }
101
102 protected function initializeGenerator()
103 {
104 while ($this->handle->valid()) {
105 $this->offsetBefore = $this->handle->ftell();
106 yield $this->handle->readAndMoveNext();
107 }
108 }
109
110 #[\ReturnTypeWillChange]
111 public function current()
112 {
113 return $this->fileGenerator->current();
114 }
115
116 #[\ReturnTypeWillChange]
117 public function next()
118 {
119 $this->fileGenerator->next();
120 }
121
122 #[\ReturnTypeWillChange]
123 public function key()
124 {
125 return $this->fileGenerator->key();
126 }
127
128 #[\ReturnTypeWillChange]
129 public function valid()
130 {
131 return $this->fileGenerator->valid();
132 }
133
134 #[\ReturnTypeWillChange]
135 public function rewind()
136 {
137 $this->handle->fseek(0);
138 }
139
140 #[\ReturnTypeWillChange]
141 public function seek($offset)
142 {
143 $this->handle->fseek($offset);
144 }
145
146 public function isFinished()
147 {
148 return $this->handle->eof();
149 }
150
151 /**
152 * @param $dequeue
153 * @return mixed|void
154 */
155 public function retry($dequeue = true)
156 {
157 $this->seek($this->offsetBefore);
158
159 if ($dequeue) {
160 return $this->dequeue();
161 }
162 }
163
164 /**
165 * @param $data
166 * @return false|int
167 */
168 public function enqueue($data)
169 {
170 // Early bail: Write-only optimization
171 if ($this->isWriteOnly) {
172 $this->handle->fwrite(trim($data) . PHP_EOL);
173
174 return $this->handle->ftell();
175 }
176
177 $currentOffset = $this->handle->ftell();
178
179 $this->handle->fseek(0, SEEK_END);
180 $this->handle->flock(LOCK_EX);
181 $this->handle->fwrite(trim($data) . PHP_EOL);
182 $this->handle->flock(LOCK_UN);
183
184 $offsetEndOfQueue = $this->handle->ftell();
185 $this->handle->fseek($currentOffset);
186
187 return $offsetEndOfQueue;
188 }
189
190 /**
191 * @return mixed
192 */
193 public function dequeue()
194 {
195 if ($this->isWriteOnly) {
196 throw new \BadMethodCallException('Trying to read from read-only Queue');
197 }
198
199 $first = is_null($this->offsetBefore);
200
201 if (!$first) {
202 $this->next();
203 }
204
205 return $this->current();
206 }
207
208 /**
209 * @param array $data
210 * @return false|int
211 */
212 public function enqueueMany(array $data = [])
213 {
214 foreach ($data as $item) {
215 if (is_scalar($item)) {
216 $this->enqueue((string)$item);
217 }
218 }
219
220 return $this->handle->ftell();
221 }
222
223 public function reset()
224 {
225 $this->handle->ftruncate(0);
226 }
227
228 /**
229 * @return false|int
230 */
231 public function getOffset()
232 {
233 if (!isset($this->handle) || !$this->handle instanceof FileObject) {
234 return false;
235 }
236
237 return $this->handle->ftell();
238 }
239
240 /**
241 * @return void
242 */
243 public function shutdown()
244 {
245 if ($this->needsUnlock && $this->handle instanceof FileObject) {
246 $this->unlockObject();
247 return;
248 }
249 }
250
251 protected function unlockObject()
252 {
253 try {
254 $this->handle->flock(LOCK_UN);
255 } catch (Exception $e) {
256 $message = $e->getMessage();
257 if ($message !== 'Object not initialized') {
258 debug_log("Unable to unlock handle " . $this->taskName . '.task : ' . $message, Logger::TYPE_DEBUG);
259 }
260 } catch (Error $e) {
261 $message = $e->getMessage();
262 if ($message !== 'Object not initialized') {
263 debug_log("Unable to unlock handle " . $this->taskName . '.task : ' . $message, Logger::TYPE_DEBUG);
264 }
265 }
266 }
267 }
268