PluginProbe ʕ •ᴥ•ʔ
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization / 1.4.1
NitroPack – Performance, Page Speed & Cache Plugin for Core Web Vitals, CDN & Image Optimization v1.4.1
1.19.8 1.19.7 1.19.6 1.19.5 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.11.0 1.12.0 1.13.0 1.14.0 1.15.0 1.15.1 1.15.2 1.15.3 1.16.0 1.16.1 1.16.2 1.16.3 1.16.4 1.16.5 1.16.6 1.16.7 1.16.8 1.17.0 1.17.6 1.17.7 1.17.8 1.17.9 1.18.0 1.18.1 1.18.2 1.18.3 1.18.4 1.18.5 1.18.6 1.18.7 1.18.8 1.18.9 1.19.0 1.19.1 1.19.2 1.19.3 1.19.4 1.3.19 1.3.20 1.4.0 1.4.1 1.5.0 1.5.1 1.5.10 1.5.11 1.5.12 1.5.13 1.5.14 1.5.15 1.5.16 1.5.17 1.5.18 1.5.19 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.5.9 1.6.0 1.6.1 1.7.0 1.7.1 1.8.0 1.8.1 1.8.3 1.9.0 1.9.1 1.9.2
nitropack / nitropack-sdk / NitroPack / SDK / StorageDriver / Redis.php
nitropack / nitropack-sdk / NitroPack / SDK / StorageDriver Last commit date
Disk.php 6 years ago Redis.php 6 years ago
Redis.php
240 lines
1 <?php
2 // Disclaimer mtimes are only accurate for the final entries. But mtimes for parent directories (more than 1 level up the hierarchy) might not be updated correctly when children have been modified
3 // This is also a bad design for emulating a file system performance wise. Only use this driver when you need shared storage between multiple servers. On a single server with an SSD using the Disk diver is a better idea.
4 namespace NitroPack\SDK\StorageDriver;
5
6 class Redis {
7 private $redis;
8
9 private function preparePathInput($path) {
10 return $path == DIRECTORY_SEPARATOR ? $path : rtrim($path, DIRECTORY_SEPARATOR);
11 }
12
13 public function __construct($host = "127.0.0.1", $port = 6379, $password = NULL, $db = NULL) {
14 $this->redis = new \Redis();
15 $this->redis->connect($host, $port);
16
17 if ($password !== NULL) {
18 $this->redis->auth($password);
19 }
20
21 if ($db !== NULL) {
22 $this->redis->select($db);
23 }
24 }
25
26 public function getOsPath($parts) {
27 return implode(DIRECTORY_SEPARATOR, $parts);
28 }
29
30 public function touch($path) {
31 $path = $this->preparePathInput($path);
32 $parent = dirname($path);
33 $key = basename($path);
34 if ($this->isDir($parent)) {
35 $this->redis->hSet($parent, "::mtime::" . $key, time());
36 $this->redis->hSetNx($parent, "::content::" . $key, "");
37 return true;
38 } else {
39 return false;
40 }
41 }
42
43 public function setContent($path, $content) {
44 $path = $this->preparePathInput($path);
45 if ($this->isDir($path)) {
46 return false;
47 } else {
48 try {
49 //TODO: Create parent dir if it doesn't exist. This can impact performance though. Maybe make it optional
50 $dir = dirname($path);
51 $file = basename($path);
52 $this->redis->hMSet($dir, array(
53 "::content::" . $file => $content,
54 "::mtime::" . $file => time()
55 ));
56 } catch (\Exception $e) {
57 return false;
58 }
59 return true;
60 }
61 }
62
63 public function createDir($dir) {
64 $dir = $this->preparePathInput($dir);
65 $now = time();
66 $childDir = NULL;
67 $numDirsCreated = 0;
68 try {
69 while ($childDir !== "" && !$this->exists($dir)) {
70 $this->redis->hSet($dir, "::self::ctime::", $now);
71 $numDirsCreated++;
72 if ($childDir) {
73 $this->touch($this->getOsPath(array($dir, $childDir)));
74 }
75 $childDir = basename($dir);
76 $dir = dirname($dir);
77 }
78 if ($numDirsCreated > 0 && $childDir) {
79 $this->touch($this->getOsPath(array($dir, $childDir)));
80 }
81 } catch (\Exception $e) {
82 return false;
83 }
84
85 return true;
86 }
87
88 public function deletePath($path) {
89 $path = $this->preparePathInput($path);
90 $dirKey = dirname($path);
91 $fileName = basename($path);
92
93 try {
94 $deleted = $this->redis->hDel($dirKey, "::content::" . $fileName, "::mtime::" . $fileName);
95 if ($deleted) {
96 $this->touch($dirKey);
97 }
98 } catch (\Exception $e) {
99 return false;
100 }
101
102 return true;
103 }
104
105 public function deleteFile($path) {
106 return !$this->isDir($path) && $this->deletePath($path);
107 }
108
109 public function deleteDir($dir) {
110 $dir = $this->preparePathInput($dir);
111 try {
112 if (!$this->isDir($dir)) return true;
113 $this->trunkDir($dir) && $this->redis->unlink($dir) && $this->deletePath($dir);
114 } catch (\Exception $e) {
115 return false;
116 }
117 return true;
118 }
119
120 public function trunkDir($dir) {
121 $dir = $this->preparePathInput($dir);
122 if (!$this->isDir($dir)) return false;
123 $success = false;
124 try {
125 $this->redis->eval('
126 local cursor = "0";
127 repeat
128 local t = redis.call("SCAN", cursor, "MATCH", ARGV[1]);
129 cursor = t[1];
130 local list = t[2];
131 for i = 1, #list do
132 redis.call("UNLINK", list[i]);
133 end;
134 until cursor == "0";
135 ', array($this->getOsPath(array($dir, "*"))), 0);
136 $success = true;
137 } catch (\Exception $e) {
138 // TODO: Log an error
139 }
140 return $success;
141 }
142
143 public function isDirEmpty($dir) {
144 $dir = $this->preparePathInput($dir);
145 return (int)$this->redis->hLen($dir) <= 1;
146 }
147
148 private function isDir($dir) {
149 $dir = $this->preparePathInput($dir);
150 return !!$this->redis->hLen($dir); // if this is a non-empty sorted set then it is a dir
151 }
152
153 public function dirForeach($dir, $callback) {
154 $dir = $this->preparePathInput($dir);
155 if (!$this->isDir($dir)) return false;
156 $it = NULL;
157 $prevScanMode = $this->redis->getOption(\Redis::OPT_SCAN);
158 $this->redis->setOption(\Redis::OPT_SCAN, \Redis::SCAN_RETRY);
159 try {
160 while($entries = $this->redis->hScan($dir, $it, "::mtime::*")) {
161 foreach($entries as $entry => $mtime) {
162 $entry = substr($entry, 9);//remove the ::mtime:: prefix
163 $path = $dir != DIRECTORY_SEPARATOR ? $this->getOsPath(array($dir, $entry)) : $dir . $entry;
164 call_user_func($callback, $path);
165 }
166 }
167 } catch (\Exception $e) {
168 // TODO: Log an error
169 return false;
170 } finally {
171 $this->redis->setOption(\Redis::OPT_SCAN, $prevScanMode);
172 }
173 return true;
174 }
175
176 public function mtime($path) {
177 $path = $this->preparePathInput($path);
178 $dir = dirname($path);
179 $file = basename($path);
180 return $this->redis->hGet($dir, "::mtime::" . $file);
181 }
182
183 public function exists($path) {
184 $path = $this->preparePathInput($path);
185 $dir = dirname($path);
186 $file = basename($path);
187 return $this->redis->hExists($dir, "::mtime::" . $file);
188 }
189
190 public function getContent($path) {
191 $path = $this->preparePathInput($path);
192 if ($this->isDir($path)) {
193 return false;
194 } else {
195 $dir = dirname($path);
196 $file = basename($path);
197 return $this->redis->hGet($dir, "::content::" . $file);
198 }
199 }
200
201 public function rename($oldKey, $newKey, $innerCall = false) {
202 $oldKey = $this->preparePathInput($oldKey);
203 $newKey = $this->preparePathInput($newKey);
204 if ($this->exists($newKey)) return false;
205
206 $success = false;
207
208 try {
209 $isDir = $this->isDir($oldKey);
210 if (!$isDir) {
211 $content = $this->getContent($oldKey);
212 $this->deleteFile($oldKey);
213 $this->setContent($newKey, $content);
214 } else {
215 $this->deletePath($oldKey);
216 $this->createDir($newKey);
217 $this->redis->rename($oldKey, $newKey);
218 $this->redis->eval('
219 local cursor = "0";
220 repeat
221 local t = redis.call("SCAN", cursor, "MATCH", ARGV[1]);
222 cursor = t[1];
223 local list = t[2];
224 for i = 1, #list do
225 local s = list[i];
226 local changed = s:gsub(ARGV[2], ARGV[3], 1);
227 redis.call("RENAME", s, changed);
228 end;
229 until cursor == "0";
230 ', array($this->getOsPath(array($oldKey, "*")), $oldKey . DIRECTORY_SEPARATOR, $newKey . DIRECTORY_SEPARATOR), 0);
231 }
232
233 $success = true;
234 } catch (\Exception $e) {
235 // TODO: Log an error
236 }
237 return $success;
238 }
239 }
240