Cutelyst  3.3.0
cuteleeview.cpp
1 /*
2  * Copyright (C) 2020-2022 Daniel Nicoletti <dantti12@gmail.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 #include "cuteleeview_p.h"
19 #include "cutelystcutelee.h"
20 
21 #include "application.h"
22 #include "context.h"
23 #include "action.h"
24 #include "response.h"
25 #include "config.h"
26 
27 #include <cutelee/qtlocalizer.h>
28 #include <cutelee/metatype.h>
29 
30 #include <QString>
31 #include <QDirIterator>
32 #include <QtCore/QLoggingCategory>
33 #include <QTranslator>
34 
35 Q_LOGGING_CATEGORY(CUTELYST_CUTELEE, "cutelyst.cutelee", QtWarningMsg)
36 
37 using namespace Cutelyst;
38 
39 CUTELEE_BEGIN_LOOKUP(ParamsMultiMap)
40 return object.value(property);
41 CUTELEE_END_LOOKUP
42 
43 CUTELEE_BEGIN_LOOKUP_PTR(Cutelyst::Request)
44 return object->property(property.toLatin1().constData());
45 CUTELEE_END_LOOKUP
46 
47 CuteleeView::CuteleeView(QObject *parent, const QString &name) : View(new CuteleeViewPrivate, parent, name)
48 {
49  Q_D(CuteleeView);
50 
51  Cutelee::registerMetaType<ParamsMultiMap>();
52  Cutelee::registerMetaType<Cutelyst::Request*>(); // To be able to access it's properties
53 
54  d->loader = std::make_shared<Cutelee::FileSystemTemplateLoader>();
55 
56  d->engine = new Cutelee::Engine(this);
57  d->engine->addTemplateLoader(d->loader);
58 
59  // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of cutelee engine
60  const QByteArrayList dirs = QByteArrayList{ QByteArrayLiteral(CUTELYST_PLUGINS_DIR) } + qgetenv("CUTELYST_PLUGINS_DIR").split(';');
61  for (const QByteArray &dir : dirs) {
62  d->engine->addPluginPath(QString::fromLocal8Bit(dir));
63  }
64 
65  d->engine->insertDefaultLibrary(QStringLiteral("cutelee_cutelyst"), new CutelystCutelee(d->engine));
66 
67  auto app = qobject_cast<Application *>(parent);
68  if (app) {
69  // make sure templates can be found on the current directory
70  setIncludePaths({ app->config(QStringLiteral("root")).toString() });
71 
72  // If CUTELYST_VAR is set the template might have become
73  // {{ Cutelyst.req.base }} instead of {{ c.req.base }}
74  d->cutelystVar = app->config(QStringLiteral("CUTELYST_VAR"), QStringLiteral("c")).toString();
75 
76  app->loadTranslations(QStringLiteral("plugin_view_cutelee"));
77  } else {
78  // make sure templates can be found on the current directory
80  }
81 }
82 
83 QStringList CuteleeView::includePaths() const
84 {
85  Q_D(const CuteleeView);
86  return d->includePaths;
87 }
88 
90 {
91  Q_D(CuteleeView);
92  d->loader->setTemplateDirs(paths);
93  d->includePaths = paths;
94  Q_EMIT changed();
95 }
96 
97 QString CuteleeView::templateExtension() const
98 {
99  Q_D(const CuteleeView);
100  return d->extension;
101 }
102 
104 {
105  Q_D(CuteleeView);
106  d->extension = extension;
107  Q_EMIT changed();
108 }
109 
110 QString CuteleeView::wrapper() const
111 {
112  Q_D(const CuteleeView);
113  return d->wrapper;
114 }
115 
117 {
118  Q_D(CuteleeView);
119  d->wrapper = name;
120  Q_EMIT changed();
121 }
122 
123 void CuteleeView::setCache(bool enable)
124 {
125  Q_D(CuteleeView);
126 
127  if (enable && d->cache) {
128  return; // already enabled
129  }
130 
131  delete d->engine;
132  d->engine = new Cutelee::Engine(this);
133 
134  if (enable) {
135  d->cache = std::make_shared<Cutelee::CachingLoaderDecorator>(d->loader);
136  d->engine->addTemplateLoader(d->cache);
137  } else {
138  d->cache = {};
139  d->engine->addTemplateLoader(d->loader);
140  }
141  Q_EMIT changed();
142 }
143 
144 Cutelee::Engine *CuteleeView::engine() const
145 {
146  Q_D(const CuteleeView);
147  return d->engine;
148 }
149 
151 {
152  Q_D(CuteleeView);
153 
154  if (!isCaching()) {
155  setCache(true);
156  }
157 
158  const auto includePaths = d->includePaths;
159  for (const QString &includePath : includePaths) {
160  QDirIterator it(includePath, {
161  QLatin1Char('*') + d->extension
162  },
165  while (it.hasNext()) {
166  QString path = it.next();
167  path.remove(includePath);
168  if (path.startsWith(QLatin1Char('/'))) {
169  path.remove(0, 1);
170  }
171 
172  if (d->cache->canLoadTemplate(path)) {
173  d->cache->loadByName(path, d->engine);
174  }
175  }
176  }
177 }
178 
180 {
181  Q_D(const CuteleeView);
182  return !!d->cache;
183 }
184 
186 {
187  Q_D(const CuteleeView);
188 
189  QByteArray ret;
190  c->setStash(d->cutelystVar, QVariant::fromValue(c));
191  const QVariantHash stash = c->stash();
192  auto it = stash.constFind(QStringLiteral("template"));
193  QString templateFile;
194  if (it != stash.constEnd()) {
195  templateFile = it.value().toString();
196  } else {
197  if (c->action() && !c->action()->reverse().isEmpty()) {
198  templateFile = c->action()->reverse() + d->extension;
199  if (templateFile.startsWith(QLatin1Char('/'))) {
200  templateFile.remove(0, 1);
201  }
202  }
203 
204  if (templateFile.isEmpty()) {
205  c->error(QStringLiteral("Cannot render template, template name or template stash key not defined"));
206  return ret;
207  }
208  }
209 
210  qCDebug(CUTELYST_CUTELEE) << "Rendering template" << templateFile;
211 
212  Cutelee::Context gc(stash);
213 
214  auto localizer = std::make_shared<Cutelee::QtLocalizer>(c->locale());
215 
216  auto transIt = d->translators.constFind(c->locale());
217  if (transIt != d->translators.constEnd()) {
218  localizer.get()->installTranslator(transIt.value(), transIt.key().name());
219  }
220 
221  auto catalogIt = d->translationCatalogs.constBegin();
222  while (catalogIt != d->translationCatalogs.constEnd()) {
223  localizer.get()->loadCatalog(catalogIt.value(), catalogIt.key());
224  ++it;
225  }
226 
227  gc.setLocalizer(localizer);
228 
229  Cutelee::Template tmpl = d->engine->loadByName(templateFile);
230  if (tmpl->error() != Cutelee::NoError) {
231  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
232  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
233  return ret;
234  }
235 
236  QString content = tmpl->render(&gc);
237  if (tmpl->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  if (!d->wrapper.isEmpty()) {
244  Cutelee::Template wrapper = d->engine->loadByName(d->wrapper);
245  if (tmpl->error() != Cutelee::NoError) {
246  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
247  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
248  return ret;
249  }
250 
251  Cutelee::SafeString safeContent(content, true);
252  gc.insert(QStringLiteral("content"), safeContent);
253  content = wrapper->render(&gc);
254 
255  if (wrapper->error() != Cutelee::NoError) {
256  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
257  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
258  return ret;
259  }
260  }
261 
262  ret = content.toUtf8();
263  return ret;
264 }
265 
266 void CuteleeView::addTranslator(const QLocale &locale, QTranslator *translator)
267 {
268  Q_D(CuteleeView);
269  Q_ASSERT_X(translator, "add translator to CuteleeView", "invalid QTranslator object");
270  d->translators.insert(locale, translator);
271 }
272 
273 void CuteleeView::addTranslator(const QString &locale, QTranslator *translator)
274 {
275  addTranslator(QLocale(locale), translator);
276 }
277 
278 void CuteleeView::addTranslationCatalog(const QString &path, const QString &catalog)
279 {
280  Q_D(CuteleeView);
281  Q_ASSERT_X(!path.isEmpty(), "add translation catalog to CuteleeView", "empty path");
282  Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to CuteleeView", "empty catalog name");
283  d->translationCatalogs.insert(catalog, path);
284 }
285 
287 {
288  Q_D(CuteleeView);
289  Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
290  d->translationCatalogs.unite(catalogs);
291 }
292 
293 QVector<QLocale> CuteleeView::loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix, const QString &suffix)
294 {
295  QVector<QLocale> locales;
296 
297  if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
298  const QDir i18nDir(directory);
299  if (Q_LIKELY(i18nDir.exists())) {
300  const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
301  const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
302  const QStringList namesFilter = QStringList({filename + _prefix + QLatin1Char('*') + _suffix});
303  const QFileInfoList tsFiles = i18nDir.entryInfoList(namesFilter, QDir::Files);
304  if (Q_LIKELY(!tsFiles.empty())) {
305  locales.reserve(tsFiles.size());
306  for (const QFileInfo &ts : tsFiles) {
307  const QString fn = ts.fileName();
308  const int prefIdx = fn.indexOf(_prefix);
309  const QString locString = fn.mid(prefIdx + _prefix.length(), fn.length() - prefIdx - _suffix.length() - _prefix.length());
310  QLocale loc(locString);
311  if (Q_LIKELY(loc.language() != QLocale::C)) {
312  auto trans = new QTranslator(this);
313  if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
314  addTranslator(loc, trans);
315  locales.append(loc);
316  qCDebug(CUTELYST_CUTELEE) << "Loaded translations for locale" << loc << "from" << ts.absoluteFilePath();
317  } else {
318  delete trans;
319  qCWarning(CUTELYST_CUTELEE) << "Can not load translations for locale" << loc;
320  }
321  } else {
322  qCWarning(CUTELYST_CUTELEE) << "Can not load translations for invalid locale string" << locString;
323  }
324  }
325  locales.squeeze();
326  } else {
327  qCWarning(CUTELYST_CUTELEE) << "Can not find translation files for" << filename << "in directory" << directory;
328  }
329  } else {
330  qCWarning(CUTELYST_CUTELEE) << "Can not load translations from not existing directory:" << directory;
331  }
332  } else {
333  qCWarning(CUTELYST_CUTELEE) << "Can not load translations for empty file name or empty path.";
334  }
335 
336  return locales;
337 }
338 
339 #include "moc_cuteleeview.cpp"
QString name() const
Definition: component.cpp:44
QString reverse() const
Definition: component.cpp:56
The Cutelyst Context.
Definition: context.h:52
void stash(const QVariantHash &unite)
Definition: context.cpp:553
QLocale locale() const noexcept
Definition: context.cpp:460
Response * res() const noexcept
Definition: context.cpp:116
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:484
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:225
bool error() const noexcept
Returns true if an error was set.
Definition: context.cpp:63
void setWrapper(const QString &name)
Sets the template wrapper name, the template will be rendered into content variable in which the wrap...
CuteleeView(QObject *parent=nullptr, const QString &name=QString())
Constructs a CuteleeView object with the given parent and name.
Definition: cuteleeview.cpp:47
void addTranslator(const QLocale &locale, QTranslator *translator)
QByteArray render(Context *c) const final
void setTemplateExtension(const QString &extension)
Sets the template extension, defaults to ".html".
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
Returns true if caching is enabled.
Cutelee::Engine * engine() const
void addTranslationCatalog(const QString &path, const QString &catalog)
void setCache(bool enable)
Sets if template caching should be done, this increases performance at the cost of higher memory usag...
void setIncludePaths(const QStringList &paths)
Sets the list of include paths which will be looked for when resolving templates files.
Definition: cuteleeview.cpp:89
void setBody(QIODevice *body)
Definition: response.cpp:114
Cutelyst View abstract view component
Definition: view.h:35
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:8
QString currentPath()
QFileInfoList entryInfoList(QDir::Filters filters, QDir::SortFlags sort) const const
bool exists() const const
QLocale::Language language() const const
Q_EMITQ_EMIT
QObject * parent() const const
QString fromLocal8Bit(const char *str, int size)
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
int length() const const
QString mid(int position, int n) const const
QString & remove(int position, int n)
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const const
QByteArray toUtf8() const const
QVariant fromValue(const T &value)
void append(const T &value)
void reserve(int size)
void squeeze()