Cutelyst  3.5.0
cuteleeview.cpp
1 /*
2  * SPDX-FileCopyrightText: (C) 2020-2022 Daniel Nicoletti <dantti12@gmail.com>
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 #include "cuteleeview_p.h"
6 #include "cutelystcutelee.h"
7 
8 #include "application.h"
9 #include "context.h"
10 #include "action.h"
11 #include "response.h"
12 #include "config.h"
13 
14 #include <cutelee/qtlocalizer.h>
15 #include <cutelee/metatype.h>
16 
17 #include <QString>
18 #include <QDirIterator>
19 #include <QtCore/QLoggingCategory>
20 #include <QTranslator>
21 
22 Q_LOGGING_CATEGORY(CUTELYST_CUTELEE, "cutelyst.cutelee", QtWarningMsg)
23 
24 using namespace Cutelyst;
25 
26 CUTELEE_BEGIN_LOOKUP(ParamsMultiMap)
27 return object.value(property);
28 CUTELEE_END_LOOKUP
29 
30 CUTELEE_BEGIN_LOOKUP_PTR(Cutelyst::Request)
31 return object->property(property.toLatin1().constData());
32 CUTELEE_END_LOOKUP
33 
34 CuteleeView::CuteleeView(QObject *parent, const QString &name) : 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 = app->config(QStringLiteral("CUTELYST_VAR"), QStringLiteral("c")).toString();
56 
57  app->loadTranslations(QStringLiteral("plugin_view_cutelee"));
58  } else {
59  // make sure templates can be found on the current directory
61  }
62 }
63 
65 {
66  Q_D(const CuteleeView);
67  return d->includePaths;
68 }
69 
71 {
72  Q_D(CuteleeView);
73  d->loader->setTemplateDirs(paths);
74  d->includePaths = paths;
75  Q_EMIT changed();
76 }
77 
79 {
80  Q_D(const CuteleeView);
81  return d->extension;
82 }
83 
85 {
86  Q_D(CuteleeView);
87  d->extension = extension;
88  Q_EMIT changed();
89 }
90 
92 {
93  Q_D(const CuteleeView);
94  return d->wrapper;
95 }
96 
98 {
99  Q_D(CuteleeView);
100  d->wrapper = name;
101  Q_EMIT changed();
102 }
103 
104 void CuteleeView::setCache(bool enable)
105 {
106  Q_D(CuteleeView);
107 
108  if (enable && d->cache) {
109  return; // already enabled
110  }
111 
112  delete d->engine;
113  d->engine = new Cutelee::Engine(this);
114 
115  if (enable) {
116  d->cache = std::make_shared<Cutelee::CachingLoaderDecorator>(d->loader);
117  d->engine->addTemplateLoader(d->cache);
118  } else {
119  d->cache = {};
120  d->engine->addTemplateLoader(d->loader);
121  }
122  d->initEngine();
123  Q_EMIT changed();
124 }
125 
126 Cutelee::Engine *CuteleeView::engine() const
127 {
128  Q_D(const CuteleeView);
129  return d->engine;
130 }
131 
133 {
134  Q_D(CuteleeView);
135 
136  if (!isCaching()) {
137  setCache(true);
138  }
139 
140  const auto includePaths = d->includePaths;
141  for (const QString &includePath : includePaths) {
142  QDirIterator it(includePath, {
143  QLatin1Char('*') + d->extension
144  },
145  QDir::Files | QDir::NoDotAndDotDot,
146  QDirIterator::Subdirectories);
147  while (it.hasNext()) {
148  QString path = it.next();
149  path.remove(includePath);
150  if (path.startsWith(QLatin1Char('/'))) {
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(QLatin1Char('/'))) {
182  templateFile.remove(0, 1);
183  }
184  }
185 
186  if (templateFile.isEmpty()) {
187  c->error(QStringLiteral("Cannot render template, template name or template stash key not defined"));
188  return ret;
189  }
190  }
191 
192  qCDebug(CUTELYST_CUTELEE) << "Rendering template" << templateFile;
193 
194  Cutelee::Context gc(stash);
195 
196  auto localizer = std::make_shared<Cutelee::QtLocalizer>(c->locale());
197 
198  auto transIt = d->translators.constFind(c->locale());
199  if (transIt != d->translators.constEnd()) {
200  localizer.get()->installTranslator(transIt.value(), transIt.key().name());
201  }
202 
203  auto catalogIt = d->translationCatalogs.constBegin();
204  while (catalogIt != d->translationCatalogs.constEnd()) {
205  localizer.get()->loadCatalog(catalogIt.value(), catalogIt.key());
206  ++it;
207  }
208 
209  gc.setLocalizer(localizer);
210 
211  Cutelee::Template tmpl = d->engine->loadByName(templateFile);
212  if (tmpl->error() != Cutelee::NoError) {
213  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
214  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
215  return ret;
216  }
217 
218  QString content = tmpl->render(&gc);
219  if (tmpl->error() != Cutelee::NoError) {
220  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
221  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
222  return ret;
223  }
224 
225  if (!d->wrapper.isEmpty()) {
226  Cutelee::Template wrapper = d->engine->loadByName(d->wrapper);
227  if (tmpl->error() != Cutelee::NoError) {
228  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
229  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
230  return ret;
231  }
232 
233  Cutelee::SafeString safeContent(content, true);
234  gc.insert(QStringLiteral("content"), safeContent);
235  content = wrapper->render(&gc);
236 
237  if (wrapper->error() != Cutelee::NoError) {
238  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
239  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
240  return ret;
241  }
242  }
243 
244  ret = content.toUtf8();
245  return ret;
246 }
247 
248 void CuteleeView::addTranslator(const QLocale &locale, QTranslator *translator)
249 {
250  Q_D(CuteleeView);
251  Q_ASSERT_X(translator, "add translator to CuteleeView", "invalid QTranslator object");
252  d->translators.insert(locale, translator);
253 }
254 
255 void CuteleeView::addTranslator(const QString &locale, QTranslator *translator)
256 {
257  addTranslator(QLocale(locale), translator);
258 }
259 
260 void CuteleeView::addTranslationCatalog(const QString &path, const QString &catalog)
261 {
262  Q_D(CuteleeView);
263  Q_ASSERT_X(!path.isEmpty(), "add translation catalog to CuteleeView", "empty path");
264  Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to CuteleeView", "empty catalog name");
265  d->translationCatalogs.insert(catalog, path);
266 }
267 
269 {
270  Q_D(CuteleeView);
271  Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
272  d->translationCatalogs.unite(catalogs);
273 }
274 
275 QVector<QLocale> CuteleeView::loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix, const QString &suffix)
276 {
277  QVector<QLocale> locales;
278 
279  if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
280  const QDir i18nDir(directory);
281  if (Q_LIKELY(i18nDir.exists())) {
282  const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
283  const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
284  const QStringList namesFilter = QStringList({filename + _prefix + QLatin1Char('*') + _suffix});
285  const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
286  if (Q_LIKELY(!tsFiles.empty())) {
287  locales.reserve(tsFiles.size());
288  for (const QFileInfo &ts : tsFiles) {
289  const QString fn = ts.fileName();
290  const int prefIdx = fn.indexOf(_prefix);
291  const QString locString = fn.mid(prefIdx + _prefix.length(), fn.length() - prefIdx - _suffix.length() - _prefix.length());
292  QLocale loc(locString);
293  if (Q_LIKELY(loc.language() != QLocale::C)) {
294  auto trans = new QTranslator(this);
295  if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
296  addTranslator(loc, trans);
297  locales.append(loc);
298  qCDebug(CUTELYST_CUTELEE) << "Loaded translations for locale" << loc << "from" << ts.absoluteFilePath();
299  } else {
300  delete trans;
301  qCWarning(CUTELYST_CUTELEE) << "Can not load translations for locale" << loc;
302  }
303  } else {
304  qCWarning(CUTELYST_CUTELEE) << "Can not load translations for invalid locale string" << locString;
305  }
306  }
307  locales.squeeze();
308  } else {
309  qCWarning(CUTELYST_CUTELEE) << "Can not find translation files for" << filename << "in directory" << directory;
310  }
311  } else {
312  qCWarning(CUTELYST_CUTELEE) << "Can not load translations from not existing directory:" << directory;
313  }
314  } else {
315  qCWarning(CUTELYST_CUTELEE) << "Can not load translations for empty file name or empty path.";
316  }
317 
318  return locales;
319 }
320 
321 void CuteleeViewPrivate::initEngine()
322 {
323  // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of cutelee engine
324  const QByteArrayList dirs = QByteArrayList{ QByteArrayLiteral(CUTELYST_PLUGINS_DIR) } + qgetenv("CUTELYST_PLUGINS_DIR").split(';');
325  for (const QByteArray &dir : dirs) {
326  engine->addPluginPath(QString::fromLocal8Bit(dir));
327  }
328 
329  engine->insertDefaultLibrary(QStringLiteral("cutelee_cutelyst"), new CutelystCutelee(engine));
330 }
331 
332 #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:50
void append(const T &value)
void addTranslator(const QLocale &locale, QTranslator *translator)
Response * res() const noexcept
Definition: context.cpp:103
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:212
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:540
QString name() const
Definition: component.cpp:31
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:471
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:70
QString reverse() const
Definition: component.cpp:43
QLocale locale() const noexcept
Definition: context.cpp:447
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:34
void setBody(QIODevice *body)
Definition: response.cpp:101
QObject * parent() const const
void setTemplateExtension(const QString &extension)
Sets the template extension, defaults to ".html".
Definition: cuteleeview.cpp:84
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:97
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
QByteArray toUtf8() const const