LCOV - code coverage report
Current view: top level - extra/webserver/pug - SPPugCache.cc (source / functions) Hit Total Coverage
Test: coverage.info Lines: 219 242 90.5 %
Date: 2024-05-12 00:16:13 Functions: 33 33 100.0 %

          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             : }

Generated by: LCOV version 1.14