Line data Source code
1 : /**
2 : Copyright (c) 2024 Stappler LLC <admin@stappler.dev>
3 :
4 : Permission is hereby granted, free of charge, to any person obtaining a copy
5 : of this software and associated documentation files (the "Software"), to deal
6 : in the Software without restriction, including without limitation the rights
7 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 : copies of the Software, and to permit persons to whom the Software is
9 : furnished to do so, subject to the following conditions:
10 :
11 : The above copyright notice and this permission notice shall be included in
12 : all copies or substantial portions of the Software.
13 :
14 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 : THE SOFTWARE.
21 : **/
22 :
23 : #include "SPPugCache.h"
24 : #include "SPPugContext.h"
25 : #include "SPPugTemplate.h"
26 : #include "SPFilesystem.h"
27 :
28 : #include "SPPlatformUnistd.h"
29 :
30 : #if LINUX
31 : #include <sys/inotify.h>
32 : #endif
33 :
34 : #ifndef SP_TERMINATED_DATA
35 : #define SP_TERMINATED_DATA(view) (view.terminated()?view.data():view.str<memory::PoolInterface>().data())
36 : #endif
37 :
38 : namespace STAPPLER_VERSIONIZED stappler::pug {
39 :
40 : #if LINUX
41 : static int s_FileNotifyMask = IN_CLOSE_WRITE;
42 : #endif
43 :
44 250 : Rc<FileRef> FileRef::read(memory::pool_t *p, FilePath path, Template::Options opts, const Callback<void(const StringView &)> &cb, int watch, int wId) {
45 250 : auto fpath = path.get();
46 250 : if (filesystem::exists(fpath)) {
47 250 : auto pool = memory::pool::create(p);
48 :
49 250 : memory::pool::context ctx(pool);
50 250 : return Rc<FileRef>::alloc(pool, path, opts, cb, watch, wId);
51 250 : }
52 :
53 0 : return nullptr;
54 : }
55 :
56 450 : Rc<FileRef> FileRef::read(memory::pool_t *p, String && content, bool isTemplate, Template::Options opts, const Callback<void(const StringView &)> &cb) {
57 450 : auto pool = memory::pool::create(p);
58 :
59 450 : memory::pool::context ctx(pool);
60 900 : return Rc<FileRef>::alloc(pool, move(content), isTemplate, opts, cb);
61 450 : }
62 :
63 250 : FileRef::FileRef(memory::pool_t *pool, const FilePath &path, Template::Options opts, const Callback<void(const StringView &)> &cb, int watch, int wId)
64 250 : : _pool(pool), _opts(opts) {
65 250 : auto fpath = path.get();
66 :
67 250 : filesystem::Stat stat;
68 250 : filesystem::stat(fpath, stat);
69 :
70 250 : _mtime = stat.mtime;
71 250 : _content.resize(stat.size);
72 250 : filesystem::readIntoBuffer((uint8_t *)_content.data(), fpath);
73 :
74 250 : if (_content.size() > 0) {
75 250 : if (wId < 0 && watch >= 0) {
76 : #if LINUX
77 225 : _watch = inotify_add_watch(watch, SP_TERMINATED_DATA(fpath), s_FileNotifyMask);
78 225 : if (_watch == -1 && errno == ENOSPC) {
79 0 : cb("inotify limit is reached: fall back to timed watcher");
80 : }
81 : #endif
82 : } else {
83 25 : _watch = wId;
84 : }
85 250 : _valid = true;
86 : }
87 250 : if (_valid && (fpath.ends_with(".pug") || fpath.ends_with(".stl") || fpath.ends_with(".spug"))) {
88 175 : _template = Template::read(_pool, _content, opts, cb);
89 175 : if (!_template) {
90 0 : _valid = false;
91 : }
92 : }
93 250 : }
94 :
95 450 : FileRef::FileRef(memory::pool_t *pool, String &&src, bool isTemplate, Template::Options opts, const Callback<void(const StringView &)> &cb)
96 450 : : _pool(pool), _content(move(src)), _opts(opts) {
97 450 : if (_content.size() > 0) {
98 450 : _valid = true;
99 : }
100 450 : if (isTemplate && _valid) {
101 300 : _template = Template::read(_pool, _content, opts, cb);
102 300 : if (!_template) {
103 0 : _valid = false;
104 : }
105 : }
106 450 : }
107 :
108 750 : FileRef::~FileRef() {
109 375 : if (_pool) {
110 375 : memory::pool::destroy(_pool);
111 : }
112 750 : }
113 :
114 50 : const String &FileRef::getContent() const {
115 50 : return _content;
116 : }
117 :
118 1625 : const Template *FileRef::getTemplate() const {
119 1625 : return _template;
120 : }
121 :
122 550 : int FileRef::getWatch() const {
123 550 : return _watch;
124 : }
125 :
126 275 : Time FileRef::getMtime() const {
127 275 : return _mtime;
128 : }
129 :
130 250 : bool FileRef::isValid() const {
131 250 : return _valid;
132 : }
133 :
134 575 : const Template::Options &FileRef::getOpts() const {
135 575 : return _opts;
136 : }
137 :
138 25 : int FileRef::regenerate(int notify, StringView fpath) {
139 25 : if (_watch >= 0) {
140 : #if LINUX
141 25 : inotify_rm_watch(notify, _watch);
142 25 : _watch = inotify_add_watch(notify, SP_TERMINATED_DATA(fpath), s_FileNotifyMask);
143 25 : return _watch;
144 : #endif
145 : }
146 0 : return 0;
147 : }
148 :
149 50 : Cache::Cache(Template::Options opts, const Function<void(const StringView &)> &err)
150 50 : : _pool(memory::pool::acquire()), _opts(opts), _errorCallback(err) {
151 : #if LINUX
152 50 : _inotify = inotify_init1(IN_NONBLOCK);
153 : #endif
154 50 : if (_inotify != -1) {
155 50 : _inotifyAvailable = true;
156 : }
157 50 : }
158 :
159 25 : Cache::~Cache() {
160 25 : if (_inotify > 0) {
161 : #if LINUX
162 250 : for (auto &it : _templates) {
163 225 : auto fd = it.second->getWatch();
164 225 : if (fd >= 0) {
165 100 : inotify_rm_watch(_inotify, fd);
166 : }
167 : }
168 : #endif
169 :
170 25 : close(_inotify);
171 : }
172 25 : }
173 :
174 50 : void Cache::update(int watch, bool regenerate) {
175 50 : std::unique_lock<Mutex> lock(_mutex);
176 50 : auto it = _watches.find(watch);
177 50 : if (it != _watches.end()) {
178 50 : auto tIt = _templates.find(it->second);
179 50 : if (tIt != _templates.end()) {
180 50 : if (regenerate) {
181 25 : _watches.erase(it);
182 25 : if (auto tpl = openTemplate(it->second, -1, tIt->second->getOpts())) {
183 25 : tIt->second = tpl;
184 25 : watch = tIt->second->getWatch();
185 25 : if (watch < 0) {
186 0 : _inotifyAvailable = false;
187 : } else {
188 25 : _watches.emplace(watch, tIt->first);
189 : }
190 25 : }
191 : } else {
192 25 : if (auto tpl = openTemplate(it->second, tIt->second->getWatch(), tIt->second->getOpts())) {
193 25 : tIt->second = tpl;
194 25 : }
195 : }
196 : }
197 : }
198 50 : }
199 :
200 25 : void Cache::update(memory::pool_t *pool, bool force) {
201 25 : memory::pool::context ctx(pool);
202 225 : for (auto &it : _templates) {
203 200 : if (it.second->getMtime() != Time()) {
204 75 : filesystem::Stat stat;
205 75 : filesystem::stat(it.first, stat);
206 75 : if (stat.mtime != it.second->getMtime() || force) {
207 75 : if (auto tpl = openTemplate(it.first, -1, it.second->getOpts())) {
208 75 : it.second = tpl;
209 75 : }
210 : }
211 : }
212 : }
213 25 : }
214 :
215 25 : int Cache::getNotify() const {
216 25 : return _inotify;
217 : }
218 :
219 25 : bool Cache::isNotifyAvailable() {
220 25 : std::unique_lock<Mutex> lock(_mutex);
221 25 : return _inotifyAvailable;
222 25 : }
223 :
224 25 : void Cache::regenerate(StringView key) {
225 25 : if (_inotifyAvailable) {
226 25 : auto it = _templates.find(key);
227 25 : if (it != _templates.end()) {
228 25 : _watches.erase(it->second->getWatch());
229 25 : auto watch = it->second->regenerate(_inotify, key);
230 25 : _watches.emplace(watch, it->first);
231 : }
232 : }
233 25 : }
234 :
235 25 : void Cache::drop(StringView key) {
236 25 : auto it = _templates.find(key);
237 25 : if (it != _templates.end()) {
238 25 : _templates.erase(it);
239 : }
240 25 : }
241 :
242 400 : bool Cache::runTemplate(const StringView &ipath, const RunCallback &cb, const OutStream &out) {
243 400 : Rc<FileRef> tpl = acquireTemplate(ipath, true, _opts);
244 400 : if (!tpl) {
245 25 : tpl = acquireTemplate(filesystem::writablePath<memory::PoolInterface>(ipath), false, _opts);
246 : }
247 :
248 800 : return runTemplate(tpl, ipath, cb, out, tpl->getTemplate()->getOptions());
249 400 : }
250 :
251 50 : bool Cache::runTemplate(const StringView &ipath, const RunCallback &cb, const OutStream &out, Template::Options opts) {
252 50 : Rc<FileRef> tpl = acquireTemplate(ipath, true, opts);
253 50 : if (!tpl) {
254 25 : tpl = acquireTemplate(filesystem::writablePath<memory::PoolInterface>(ipath), false, opts);
255 : }
256 :
257 100 : return runTemplate(tpl, ipath, cb, out, opts);
258 50 : }
259 :
260 100 : bool Cache::addFile(StringView path) {
261 100 : std::unique_lock<Mutex> lock(_mutex);
262 100 : auto it = _templates.find(path);
263 100 : if (it == _templates.end()) {
264 75 : memory::pool::context ctx(_pool);
265 75 : if (auto tpl = openTemplate(path, -1, _opts)) {
266 75 : auto it = _templates.emplace(path.pdup(_templates.get_allocator()), tpl).first;
267 75 : if (tpl->getWatch() >= 0) {
268 75 : _watches.emplace(tpl->getWatch(), it->first);
269 : }
270 75 : return true;
271 75 : }
272 75 : } else {
273 25 : onError(string::toString<memory::PoolInterface>("Already added: '", path, "'"));
274 : }
275 25 : return false;
276 100 : }
277 :
278 150 : bool Cache::addContent(StringView key, String &&data) {
279 150 : std::unique_lock<Mutex> lock(_mutex);
280 150 : auto it = _templates.find(key);
281 150 : if (it == _templates.end()) {
282 150 : auto tpl = FileRef::read(_pool, move(data), false, _opts);
283 150 : _templates.emplace(key.pdup(_templates.get_allocator()), tpl);
284 150 : return true;
285 150 : } else {
286 0 : onError(string::toString<memory::PoolInterface>("Already added: '", key, "'"));
287 : }
288 0 : return false;
289 150 : }
290 :
291 275 : bool Cache::addTemplate(StringView key, String &&data) {
292 275 : return addTemplate(key, move(data), _opts);
293 : }
294 :
295 300 : bool Cache::addTemplate(StringView key, String &&data, Template::Options opts) {
296 300 : std::unique_lock<Mutex> lock(_mutex);
297 300 : auto it = _templates.find(key);
298 300 : if (it == _templates.end()) {
299 300 : auto tpl = FileRef::read(_pool, move(data), true, opts, [&] (const StringView &err) SP_COVERAGE_TRIVIAL {
300 : std::cout << key << ":\n";
301 : std::cout << err << "\n";
302 600 : });
303 300 : _templates.emplace(key.pdup(_templates.get_allocator()), tpl);
304 300 : return true;
305 300 : } else {
306 0 : onError(string::toString<memory::PoolInterface>("Already added: '", key, "'"));
307 : }
308 0 : return false;
309 300 : }
310 :
311 100 : Rc<FileRef> Cache::get(StringView key) const {
312 100 : auto it = _templates.find(key);
313 100 : if (it != _templates.end()) {
314 100 : return it->second;
315 : }
316 0 : return nullptr;
317 : }
318 :
319 1200 : Rc<FileRef> Cache::acquireTemplate(StringView path, bool readOnly, const Template::Options &opts) {
320 1200 : std::unique_lock<Mutex> lock(_mutex);
321 1200 : auto it = _templates.find(path);
322 1200 : if (it != _templates.end()) {
323 1100 : return it->second;
324 100 : } else if (!readOnly) {
325 50 : if (auto tpl = openTemplate(path, -1, opts)) {
326 50 : auto it = _templates.emplace(path.pdup(_templates.get_allocator()), tpl).first;
327 50 : if (tpl->getWatch() >= 0) {
328 50 : _watches.emplace(tpl->getWatch(), it->first);
329 : }
330 50 : return tpl;
331 50 : }
332 : }
333 50 : return nullptr;
334 1200 : }
335 :
336 250 : Rc<FileRef> Cache::openTemplate(StringView path, int wId, const Template::Options &opts) {
337 : auto ret = FileRef::read(_pool, FilePath(path), opts, [&] (const StringView &err) SP_COVERAGE_TRIVIAL {
338 : std::cout << path << ":\n";
339 : std::cout << err << "\n";
340 250 : }, _inotify, wId);
341 250 : if (!ret) {
342 0 : onError(string::toString<memory::PoolInterface>("File not found: ", path));
343 250 : } else if (ret->isValid()) {
344 250 : return ret;
345 : }
346 0 : return nullptr;
347 250 : }
348 :
349 450 : bool Cache::runTemplate(Rc<FileRef> tpl, StringView ipath, const RunCallback &cb, const OutStream &out, Template::Options opts) {
350 450 : if (tpl) {
351 450 : if (auto t = tpl->getTemplate()) {
352 450 : auto iopts = tpl->getOpts();
353 450 : Context exec;
354 450 : exec.loadDefaults();
355 450 : exec.setIncludeCallback([this, iopts] (const StringView &path, Context &exec, const OutStream &out, Template::RunContext &rctx) -> bool {
356 700 : Rc<FileRef> tpl = acquireTemplate(path, true, iopts);
357 700 : if (!tpl) {
358 0 : tpl = acquireTemplate(filesystem::writablePath<memory::PoolInterface>(path), false, iopts);
359 : }
360 :
361 700 : if (!tpl) {
362 0 : return false;
363 : }
364 :
365 700 : bool ret = false;
366 700 : if (const Template *t = tpl->getTemplate()) {
367 675 : ret = t->run(exec, out, rctx);
368 : } else {
369 25 : out << tpl->getContent();
370 25 : ret = true;
371 : }
372 :
373 700 : return ret;
374 700 : });
375 450 : if (cb != nullptr) {
376 450 : if (!cb(exec, *t)) {
377 0 : return false;
378 : }
379 : }
380 450 : return t->run(exec, out, opts);
381 450 : } else {
382 0 : onError(string::toString<memory::PoolInterface>("File '", ipath, "' is not executable"));
383 : }
384 : } else {
385 0 : onError(string::toString<memory::PoolInterface>("No template '", ipath, "' found"));
386 : }
387 0 : return false;
388 : }
389 :
390 25 : void Cache::onError(const StringView &str) {
391 25 : if (str == "inotify limit is reached: fall back to timed watcher") {
392 0 : std::unique_lock<Mutex> lock(_mutex);
393 0 : _inotifyAvailable = false;
394 0 : }
395 25 : if (_errorCallback != nullptr) {
396 25 : _errorCallback(str);
397 : } else {
398 0 : std::cout << str;
399 : }
400 25 : }
401 :
402 : }
|