cutelyst  4.4.0
A C++ Web Framework built on top of Qt, using the simple approach of Catalyst (Perl) framework.
cuteleeview.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2020-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "action.h"
6 #include "application.h"
7 #include "config.h"
8 #include "context.h"
9 #include "cuteleeview_p.h"
10 #include "cutelystcutelee.h"
11 #include "response.h"
12 
13 #include <cutelee/metatype.h>
14 #include <cutelee/qtlocalizer.h>
15 
16 #include <QDirIterator>
17 #include <QString>
18 #include <QTranslator>
19 #include <QtCore/QLoggingCategory>
20 
21 Q_LOGGING_CATEGORY(CUTELYST_CUTELEE, "cutelyst.view.cutelee", QtWarningMsg)
22 
23 using namespace Cutelyst;
24 
25 CUTELEE_BEGIN_LOOKUP(ParamsMultiMap)
26 return object.value(property);
27 CUTELEE_END_LOOKUP
28 
29 CUTELEE_BEGIN_LOOKUP_PTR(Cutelyst::Request)
30 return object->property(property.toLatin1().constData());
31 CUTELEE_END_LOOKUP
32 
34  : View(new CuteleeViewPrivate, parent, name)
35 {
36  Q_D(CuteleeView);
37 
38  Cutelee::registerMetaType<ParamsMultiMap>();
39  Cutelee::registerMetaType<Cutelyst::Request *>(); // To be able to access it's properties
40 
41  d->loader = std::make_shared<Cutelee::FileSystemTemplateLoader>();
42 
43  d->engine = new Cutelee::Engine(this);
44  d->engine->addTemplateLoader(d->loader);
45 
46  d->initEngine();
47 
48  auto app = qobject_cast<Application *>(parent);
49  if (app) {
50  // make sure templates can be found on the current directory
51  setIncludePaths({app->config(QStringLiteral("root")).toString()});
52 
53  // If CUTELYST_VAR is set the template might have become
54  // {{ Cutelyst.req.base }} instead of {{ c.req.base }}
55  d->cutelystVar =
56  app->config(QStringLiteral("CUTELYST_VAR"), QStringLiteral("c")).toString();
57 
58  app->loadTranslations(QStringLiteral("plugin_view_cutelee"));
59  } else {
60  // make sure templates can be found on the current directory
62  }
63 }
64 
65 QStringList CuteleeView::includePaths() const
66 {
67  Q_D(const CuteleeView);
68  return d->includePaths;
69 }
70 
72 {
73  Q_D(CuteleeView);
74  d->loader->setTemplateDirs(paths);
75  d->includePaths = paths;
76  Q_EMIT changed();
77 }
78 
79 QString CuteleeView::templateExtension() const
80 {
81  Q_D(const CuteleeView);
82  return d->extension;
83 }
84 
86 {
87  Q_D(CuteleeView);
88  d->extension = extension;
89  Q_EMIT changed();
90 }
91 
92 QString CuteleeView::wrapper() const
93 {
94  Q_D(const CuteleeView);
95  return d->wrapper;
96 }
97 
99 {
100  Q_D(CuteleeView);
101  d->wrapper = name;
102  Q_EMIT changed();
103 }
104 
105 void CuteleeView::setCache(bool enable)
106 {
107  Q_D(CuteleeView);
108 
109  if (enable && d->cache) {
110  return; // already enabled
111  }
112 
113  delete d->engine;
114  d->engine = new Cutelee::Engine(this);
115 
116  if (enable) {
117  d->cache = std::make_shared<Cutelee::CachingLoaderDecorator>(d->loader);
118  d->engine->addTemplateLoader(d->cache);
119  } else {
120  d->cache = {};
121  d->engine->addTemplateLoader(d->loader);
122  }
123  d->initEngine();
124  Q_EMIT changed();
125 }
126 
127 Cutelee::Engine *CuteleeView::engine() const
128 {
129  Q_D(const CuteleeView);
130  return d->engine;
131 }
132 
134 {
135  Q_D(CuteleeView);
136 
137  if (!isCaching()) {
138  setCache(true);
139  }
140 
141  const auto includePaths = d->includePaths;
142  for (const QString &includePath : includePaths) {
143  QDirIterator it(includePath,
144  {QLatin1Char('*') + d->extension},
147  while (it.hasNext()) {
148  QString path = it.next();
149  path.remove(includePath);
150  if (path.startsWith(u'/')) {
151  path.remove(0, 1);
152  }
153 
154  if (d->cache->canLoadTemplate(path)) {
155  d->cache->loadByName(path, d->engine);
156  }
157  }
158  }
159 }
160 
162 {
163  Q_D(const CuteleeView);
164  return !!d->cache;
165 }
166 
168 {
169  Q_D(const CuteleeView);
170 
171  QByteArray ret;
172  c->setStash(d->cutelystVar, QVariant::fromValue(c));
173  const QVariantHash stash = c->stash();
174  auto it = stash.constFind(QStringLiteral("template"));
175  QString templateFile;
176  if (it != stash.constEnd()) {
177  templateFile = it.value().toString();
178  } else {
179  if (c->action() && !c->action()->reverse().isEmpty()) {
180  templateFile = c->action()->reverse() + d->extension;
181  if (templateFile.startsWith(u'/')) {
182  templateFile.remove(0, 1);
183  }
184  }
185 
186  if (templateFile.isEmpty()) {
187  c->appendError(QStringLiteral(
188  "Cannot render template, template name or template stash key not defined"));
189  return ret;
190  }
191  }
192 
193  qCDebug(CUTELYST_CUTELEE) << "Rendering template" << templateFile;
194 
195  Cutelee::Context gc(stash);
196 
197  auto localizer = std::make_shared<Cutelee::QtLocalizer>(c->locale());
198 
199  auto transIt = d->translators.constFind(c->locale());
200  if (transIt != d->translators.constEnd()) {
201  localizer.get()->installTranslator(transIt.value(), transIt.key().name());
202  }
203 
204  auto catalogIt = d->translationCatalogs.constBegin();
205  while (catalogIt != d->translationCatalogs.constEnd()) {
206  localizer.get()->loadCatalog(catalogIt.value(), catalogIt.key());
207  ++it;
208  }
209 
210  gc.setLocalizer(localizer);
211 
212  Cutelee::Template tmpl = d->engine->loadByName(templateFile);
213  if (tmpl->error() != Cutelee::NoError) {
214  //% "Internal server error."
215  c->res()->setBody(c->qtTrId("cutelyst-cuteleeview-err-internal-server"));
216  c->appendError(QLatin1String("Error while rendering template: ") + tmpl->errorString());
217  return ret;
218  }
219 
220  QString content = tmpl->render(&gc);
221  if (tmpl->error() != Cutelee::NoError) {
222  c->res()->setBody(c->qtTrId("cutelyst-cuteleeview-err-internal-server"));
223  c->appendError(QLatin1String("Error while rendering template: ") + tmpl->errorString());
224  return ret;
225  }
226 
227  if (!d->wrapper.isEmpty()) {
228  Cutelee::Template wrapper = d->engine->loadByName(d->wrapper);
229  if (tmpl->error() != Cutelee::NoError) {
230  c->res()->setBody(c->qtTrId("cutelyst-cuteleeview-err-internal-server"));
231  c->appendError(QLatin1String("Error while rendering template: ") + tmpl->errorString());
232  return ret;
233  }
234 
235  Cutelee::SafeString safeContent(content, true);
236  gc.insert(QStringLiteral("content"), safeContent);
237  content = wrapper->render(&gc);
238 
239  if (wrapper->error() != Cutelee::NoError) {
240  c->res()->setBody(c->qtTrId("cutelyst-cuteleeview-err-internal-server"));
241  c->appendError(QLatin1String("Error while rendering template: ") + tmpl->errorString());
242  return ret;
243  }
244  }
245 
246  ret = content.toUtf8();
247  return ret;
248 }
249 
250 void CuteleeView::addTranslator(const QLocale &locale, QTranslator *translator)
251 {
252  Q_D(CuteleeView);
253  Q_ASSERT_X(translator, "add translator to CuteleeView", "invalid QTranslator object");
254  d->translators.insert(locale, translator);
255 }
256 
257 void CuteleeView::addTranslator(const QString &locale, QTranslator *translator)
258 {
259  addTranslator(QLocale(locale), translator);
260 }
261 
262 void CuteleeView::addTranslationCatalog(const QString &path, const QString &catalog)
263 {
264  Q_D(CuteleeView);
265  Q_ASSERT_X(!path.isEmpty(), "add translation catalog to CuteleeView", "empty path");
266  Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to CuteleeView", "empty catalog name");
267  d->translationCatalogs.insert(catalog, path);
268 }
269 
271 {
272  Q_D(CuteleeView);
273  Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
274  d->translationCatalogs.unite(catalogs);
275 }
276 
278  const QString &directory,
279  const QString &prefix,
280  const QString &suffix)
281 {
282  QVector<QLocale> locales;
283 
284  if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
285  const QDir i18nDir(directory);
286  if (Q_LIKELY(i18nDir.exists())) {
287  const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
288  const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
289  const QStringList namesFilter =
290  QStringList({filename + _prefix + QLatin1Char('*') + _suffix});
291  const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
292  if (Q_LIKELY(!tsFiles.empty())) {
293  locales.reserve(tsFiles.size());
294  for (const QFileInfo &ts : tsFiles) {
295  const QString fn = ts.fileName();
296  const int prefIdx = fn.indexOf(_prefix);
297  const QString locString =
298  fn.mid(prefIdx + _prefix.length(),
299  fn.length() - prefIdx - _suffix.length() - _prefix.length());
300  QLocale loc(locString);
301  if (Q_LIKELY(loc.language() != QLocale::C)) {
302  auto trans = new QTranslator(this);
303  if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
304  addTranslator(loc, trans);
305  locales.append(loc);
306  qCDebug(CUTELYST_CUTELEE) << "Loaded translations for locale" << loc
307  << "from" << ts.absoluteFilePath();
308  } else {
309  delete trans;
310  qCWarning(CUTELYST_CUTELEE)
311  << "Can not load translations for locale" << loc;
312  }
313  } else {
314  qCWarning(CUTELYST_CUTELEE)
315  << "Can not load translations for invalid locale string" << locString;
316  }
317  }
318  locales.squeeze();
319  } else {
320  qCWarning(CUTELYST_CUTELEE) << "Can not find translation files for" << filename
321  << "in directory" << directory;
322  }
323  } else {
324  qCWarning(CUTELYST_CUTELEE)
325  << "Can not load translations from not existing directory:" << directory;
326  }
327  } else {
328  qCWarning(CUTELYST_CUTELEE)
329  << "Can not load translations for empty file name or empty path.";
330  }
331 
332  return locales;
333 }
334 
335 void CuteleeViewPrivate::initEngine()
336 {
337  // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of cutelee engine
338  const QByteArrayList dirs = QByteArrayList{QByteArrayLiteral(CUTELYST_PLUGINS_DIR)} +
339  qgetenv("CUTELYST_PLUGINS_DIR").split(';');
340  for (const QByteArray &dir : dirs) {
341  engine->addPluginPath(QString::fromLocal8Bit(dir));
342  }
343 
344  engine->insertDefaultLibrary(QStringLiteral("cutelee_cutelyst"), new CutelystCutelee(engine));
345 }
346 
347 #include "moc_cuteleeview.cpp"
QString reverse() const noexcept
Definition: component.cpp:45
QString name() const noexcept
Definition: component.cpp:33
The Cutelyst Context.
Definition: context.h:42
void stash(const QVariantHash &unite)
Definition: context.cpp:562
QLocale locale() const noexcept
Definition: context.cpp:460
Response * res() const noexcept
Definition: context.cpp:103
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:212
QString qtTrId(const char *id, int n=-1) const
Definition: context.h:656
Action * action
Definition: context.h:47
void appendError(const QString &error)
Definition: context.cpp:56
A view that renders templates using Cutelee engine.
Definition: cuteleeview.h:139
void setWrapper(const QString &name)
Definition: cuteleeview.cpp:98
CuteleeView(QObject *parent=nullptr, const QString &name=QString())
Definition: cuteleeview.cpp:33
void addTranslator(const QLocale &locale, QTranslator *translator)
void setTemplateExtension(const QString &extension)
Definition: cuteleeview.cpp:85
void addTranslationCatalogs(const QMultiHash< QString, QString > &catalogs)
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
bool isCaching() const
Cutelee::Engine * engine() const
void addTranslationCatalog(const QString &path, const QString &catalog)
QByteArray render(Context *c) const override final
void setCache(bool enable)
void setIncludePaths(const QStringList &paths)
Definition: cuteleeview.cpp:71
A request.
Definition: request.h:42
void setBody(QIODevice *body)
Definition: response.cpp:103
Abstract View component for Cutelyst.
Definition: view.h:25
The Cutelyst namespace holds all public Cutelyst API.
QString currentPath()
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
bool exists() const const
void append(QList::parameter_type value)
void reserve(qsizetype size)
void squeeze()
QLocale::Language language() const const
Q_EMITQ_EMIT
QObject * parent() const const
QString fromLocal8Bit(QByteArrayView str)
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QVariant fromValue(T &&value)