cutelyst  3.9.1
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.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 
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 
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 
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->error(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  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
215  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
216  return ret;
217  }
218 
219  QString content = tmpl->render(&gc);
220  if (tmpl->error() != Cutelee::NoError) {
221  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
222  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
223  return ret;
224  }
225 
226  if (!d->wrapper.isEmpty()) {
227  Cutelee::Template wrapper = d->engine->loadByName(d->wrapper);
228  if (tmpl->error() != Cutelee::NoError) {
229  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
230  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
231  return ret;
232  }
233 
234  Cutelee::SafeString safeContent(content, true);
235  gc.insert(QStringLiteral("content"), safeContent);
236  content = wrapper->render(&gc);
237 
238  if (wrapper->error() != Cutelee::NoError) {
239  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
240  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
241  return ret;
242  }
243  }
244 
245  ret = content.toUtf8();
246  return ret;
247 }
248 
249 void CuteleeView::addTranslator(const QLocale &locale, QTranslator *translator)
250 {
251  Q_D(CuteleeView);
252  Q_ASSERT_X(translator, "add translator to CuteleeView", "invalid QTranslator object");
253  d->translators.insert(locale, translator);
254 }
255 
256 void CuteleeView::addTranslator(const QString &locale, QTranslator *translator)
257 {
258  addTranslator(QLocale(locale), translator);
259 }
260 
261 void CuteleeView::addTranslationCatalog(const QString &path, const QString &catalog)
262 {
263  Q_D(CuteleeView);
264  Q_ASSERT_X(!path.isEmpty(), "add translation catalog to CuteleeView", "empty path");
265  Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to CuteleeView", "empty catalog name");
266  d->translationCatalogs.insert(catalog, path);
267 }
268 
270 {
271  Q_D(CuteleeView);
272  Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
273  d->translationCatalogs.unite(catalogs);
274 }
275 
277  const QString &directory,
278  const QString &prefix,
279  const QString &suffix)
280 {
281  QVector<QLocale> locales;
282 
283  if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
284  const QDir i18nDir(directory);
285  if (Q_LIKELY(i18nDir.exists())) {
286  const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
287  const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
288  const QStringList namesFilter =
289  QStringList({filename + _prefix + QLatin1Char('*') + _suffix});
290  const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
291  if (Q_LIKELY(!tsFiles.empty())) {
292  locales.reserve(tsFiles.size());
293  for (const QFileInfo &ts : tsFiles) {
294  const QString fn = ts.fileName();
295  const int prefIdx = fn.indexOf(_prefix);
296  const QString locString =
297  fn.mid(prefIdx + _prefix.length(),
298  fn.length() - prefIdx - _suffix.length() - _prefix.length());
299  QLocale loc(locString);
300  if (Q_LIKELY(loc.language() != QLocale::C)) {
301  auto trans = new QTranslator(this);
302  if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
303  addTranslator(loc, trans);
304  locales.append(loc);
305  qCDebug(CUTELYST_CUTELEE) << "Loaded translations for locale" << loc
306  << "from" << ts.absoluteFilePath();
307  } else {
308  delete trans;
309  qCWarning(CUTELYST_CUTELEE)
310  << "Can not load translations for locale" << loc;
311  }
312  } else {
313  qCWarning(CUTELYST_CUTELEE)
314  << "Can not load translations for invalid locale string" << locString;
315  }
316  }
317  locales.squeeze();
318  } else {
319  qCWarning(CUTELYST_CUTELEE) << "Can not find translation files for" << filename
320  << "in directory" << directory;
321  }
322  } else {
323  qCWarning(CUTELYST_CUTELEE)
324  << "Can not load translations from not existing directory:" << directory;
325  }
326  } else {
327  qCWarning(CUTELYST_CUTELEE)
328  << "Can not load translations for empty file name or empty path.";
329  }
330 
331  return locales;
332 }
333 
334 void CuteleeViewPrivate::initEngine()
335 {
336  // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of cutelee engine
337  const QByteArrayList dirs = QByteArrayList{QByteArrayLiteral(CUTELYST_PLUGINS_DIR)} +
338  qgetenv("CUTELYST_PLUGINS_DIR").split(';');
339  for (const QByteArray &dir : dirs) {
340  engine->addPluginPath(QString::fromLocal8Bit(dir));
341  }
342 
343  engine->insertDefaultLibrary(QStringLiteral("cutelee_cutelyst"), new CutelystCutelee(engine));
344 }
345 
346 #include "moc_cuteleeview.cpp"
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:49
void append(const T &value)
void addTranslator(const QLocale &locale, QTranslator *translator)
Response * res() const noexcept
Definition: context.cpp:102
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:217
QString templateExtension() const
Returns the template extension.
QString & remove(int position, int n)
QString currentPath()
void addTranslationCatalogs(const QMultiHash< QString, QString > &catalogs)
QByteArray render(Context *c) const final
The Cutelyst Context.
Definition: context.h:38
QString wrapper() const
Returns the template wrapper.
QString fromLocal8Bit(const char *str, int size)
bool exists() const const
void stash(const QVariantHash &unite)
Definition: context.cpp:566
QString name() const
Definition: component.cpp:33
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
bool isEmpty() const const
QStringList includePaths() const
Returns the list of include paths.
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:490
QLocale::Language language() const const
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
void setIncludePaths(const QStringList &paths)
Sets the list of include paths which will be looked for when resolving templates files.
Definition: cuteleeview.cpp:71
QString reverse() const
Definition: component.cpp:45
QLocale locale() const noexcept
Definition: context.cpp:466
void squeeze()
void reserve(int size)
QVariant fromValue(const T &value)
void addTranslationCatalog(const QString &path, const QString &catalog)
bool isCaching() const
Returns true if caching is enabled.
QString mid(int position, int n) const const
Cutelee::Engine * engine() const
void setCache(bool enable)
Sets if template caching should be done, this increases performance at the cost of higher memory usag...
Cutelyst View abstract view component
Definition: view.h:21
int length() const const
The Cutelyst Application.
Definition: application.h:42
CuteleeView(QObject *parent=nullptr, const QString &name=QString())
Constructs a CuteleeView object with the given parent and name.
Definition: cuteleeview.cpp:33
void setBody(QIODevice *body)
Definition: response.cpp:100
T qobject_cast(QObject *object)
QObject * parent() const const
void setTemplateExtension(const QString &extension)
Sets the template extension, defaults to ".html".
Definition: cuteleeview.cpp:85
void setWrapper(const QString &name)
Sets the template wrapper name, the template will be rendered into content variable in which the wrap...
Definition: cuteleeview.cpp:98
Q_EMITQ_EMIT
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
QByteArray toUtf8() const const