Cutelyst  2.13.0
cuteleeview.cpp
1 /*
2  * Copyright (C) 2020 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 
29 #include <QString>
30 #include <QDirIterator>
31 #include <QtCore/QLoggingCategory>
32 #include <QTranslator>
33 
34 Q_LOGGING_CATEGORY(CUTELYST_CUTELEE, "cutelyst.cutelee", QtWarningMsg)
35 
36 using namespace Cutelyst;
37 
38 CuteleeView::CuteleeView(QObject *parent, const QString &name) : View(new CuteleeViewPrivate, parent, name)
39 {
40  Q_D(CuteleeView);
41 
42  d->loader = QSharedPointer<Cutelee::FileSystemTemplateLoader>(new Cutelee::FileSystemTemplateLoader);
43 
44  d->engine = new Cutelee::Engine(this);
45  d->engine->addTemplateLoader(d->loader);
46 
47  // Set also the paths from CUTELYST_PLUGINS_DIR env variable as plugin paths of cutelee engine
48  const QByteArrayList dirs = QByteArrayList{ QByteArrayLiteral(CUTELYST_PLUGINS_DIR) } + qgetenv("CUTELYST_PLUGINS_DIR").split(';');
49  for (const QByteArray &dir : dirs) {
50  d->engine->addPluginPath(QString::fromLocal8Bit(dir));
51  }
52 
53  d->engine->insertDefaultLibrary(QStringLiteral("cutelee_cutelyst"), new CutelystCutelee(d->engine));
54 
55  auto app = qobject_cast<Application *>(parent);
56  if (app) {
57  // make sure templates can be found on the current directory
58  setIncludePaths({ app->config(QStringLiteral("root")).toString() });
59 
60  // If CUTELYST_VAR is set the template might have become
61  // {{ Cutelyst.req.base }} instead of {{ c.req.base }}
62  d->cutelystVar = app->config(QStringLiteral("CUTELYST_VAR"), QStringLiteral("c")).toString();
63 
64  app->loadTranslations(QStringLiteral("plugin_view_cutelee"));
65  } else {
66  // make sure templates can be found on the current directory
67  setIncludePaths({ QDir::currentPath() });
68  }
69 }
70 
71 QStringList CuteleeView::includePaths() const
72 {
73  Q_D(const CuteleeView);
74  return d->includePaths;
75 }
76 
77 void CuteleeView::setIncludePaths(const QStringList &paths)
78 {
79  Q_D(CuteleeView);
80  d->loader->setTemplateDirs(paths);
81  d->includePaths = paths;
82  Q_EMIT changed();
83 }
84 
85 QString CuteleeView::templateExtension() const
86 {
87  Q_D(const CuteleeView);
88  return d->extension;
89 }
90 
91 void CuteleeView::setTemplateExtension(const QString &extension)
92 {
93  Q_D(CuteleeView);
94  d->extension = extension;
95  Q_EMIT changed();
96 }
97 
98 QString CuteleeView::wrapper() const
99 {
100  Q_D(const CuteleeView);
101  return d->wrapper;
102 }
103 
104 void CuteleeView::setWrapper(const QString &name)
105 {
106  Q_D(CuteleeView);
107  d->wrapper = name;
108  Q_EMIT changed();
109 }
110 
111 void CuteleeView::setCache(bool enable)
112 {
113  Q_D(CuteleeView);
114 
115  if (enable != d->cache.isNull()) {
116  return; // already enabled
117  }
118 
119  delete d->engine;
120  d->engine = new Cutelee::Engine(this);
121 
122  if (enable) {
123  d->cache = QSharedPointer<Cutelee::CachingLoaderDecorator>(new Cutelee::CachingLoaderDecorator(d->loader));
124  d->engine->addTemplateLoader(d->cache);
125  } else {
126  d->cache.clear();
127  d->engine->addTemplateLoader(d->loader);
128  }
129  Q_EMIT changed();
130 }
131 
132 Cutelee::Engine *CuteleeView::engine() const
133 {
134  Q_D(const CuteleeView);
135  return d->engine;
136 }
137 
139 {
140  Q_D(CuteleeView);
141 
142  if (!isCaching()) {
143  setCache(true);
144  }
145 
146  const auto includePaths = d->includePaths;
147  for (const QString &includePath : includePaths) {
148  QDirIterator it(includePath, {
149  QLatin1Char('*') + d->extension
150  },
151  QDir::Files | QDir::NoDotAndDotDot,
152  QDirIterator::Subdirectories);
153  while (it.hasNext()) {
154  QString path = it.next();
155  path.remove(includePath);
156  if (path.startsWith(QLatin1Char('/'))) {
157  path.remove(0, 1);
158  }
159 
160  if (d->cache->canLoadTemplate(path)) {
161  d->cache->loadByName(path, d->engine);
162  }
163  }
164  }
165 }
166 
168 {
169  Q_D(const CuteleeView);
170  return !d->cache.isNull();
171 }
172 
173 QByteArray CuteleeView::render(Context *c) const
174 {
175  Q_D(const CuteleeView);
176 
177  QByteArray ret;
178  c->setStash(d->cutelystVar, QVariant::fromValue(c));
179  const QVariantHash stash = c->stash();
180  auto it = stash.constFind(QStringLiteral("template"));
181  QString templateFile;
182  if (it != stash.constEnd()) {
183  templateFile = it.value().toString();
184  } else {
185  if (c->action() && !c->action()->reverse().isEmpty()) {
186  templateFile = c->action()->reverse() + d->extension;
187  if (templateFile.startsWith(QLatin1Char('/'))) {
188  templateFile.remove(0, 1);
189  }
190  }
191 
192  if (templateFile.isEmpty()) {
193  c->error(QStringLiteral("Cannot render template, template name or template stash key not defined"));
194  return ret;
195  }
196  }
197 
198  qCDebug(CUTELYST_CUTELEE) << "Rendering template" << templateFile;
199 
200  Cutelee::Context gc(stash);
201 
202  auto localizer = QSharedPointer<Cutelee::QtLocalizer>::create(c->locale());
203 
204  auto transIt = d->translators.constFind(c->locale());
205  if (transIt != d->translators.constEnd()) {
206  localizer.data()->installTranslator(transIt.value(), transIt.key().name());
207  }
208 
209  auto catalogIt = d->translationCatalogs.constBegin();
210  while (catalogIt != d->translationCatalogs.constEnd()) {
211  localizer.data()->loadCatalog(catalogIt.value(), catalogIt.key());
212  ++it;
213  }
214 
215  gc.setLocalizer(localizer);
216 
217  Cutelee::Template tmpl = d->engine->loadByName(templateFile);
218  if (tmpl->error() != Cutelee::NoError) {
219  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
220  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
221  return ret;
222  }
223 
224  QString content = tmpl->render(&gc);
225  if (tmpl->error() != Cutelee::NoError) {
226  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
227  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
228  return ret;
229  }
230 
231  if (!d->wrapper.isEmpty()) {
232  Cutelee::Template wrapper = d->engine->loadByName(d->wrapper);
233  if (tmpl->error() != Cutelee::NoError) {
234  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
235  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
236  return ret;
237  }
238 
239  Cutelee::SafeString safeContent(content, true);
240  gc.insert(QStringLiteral("content"), safeContent);
241  content = wrapper->render(&gc);
242 
243  if (wrapper->error() != Cutelee::NoError) {
244  c->res()->setBody(c->translate("Cutelyst::CuteleeView", "Internal server error."));
245  c->error(QLatin1String("Error while rendering template: ") + tmpl->errorString());
246  return ret;
247  }
248  }
249 
250  ret = content.toUtf8();
251  return ret;
252 }
253 
254 void CuteleeView::addTranslator(const QLocale &locale, QTranslator *translator)
255 {
256  Q_D(CuteleeView);
257  Q_ASSERT_X(translator, "add translator to CuteleeView", "invalid QTranslator object");
258  d->translators.insert(locale, translator);
259 }
260 
261 void CuteleeView::addTranslator(const QString &locale, QTranslator *translator)
262 {
263  addTranslator(QLocale(locale), translator);
264 }
265 
266 void CuteleeView::addTranslationCatalog(const QString &path, const QString &catalog)
267 {
268  Q_D(CuteleeView);
269  Q_ASSERT_X(!path.isEmpty(), "add translation catalog to CuteleeView", "empty path");
270  Q_ASSERT_X(!catalog.isEmpty(), "add translation catalog to CuteleeView", "empty catalog name");
271  d->translationCatalogs.insert(catalog, path);
272 }
273 
274 void CuteleeView::addTranslationCatalogs(const QHash<QString, QString> &catalogs)
275 {
276  Q_D(CuteleeView);
277  Q_ASSERT_X(!catalogs.empty(), "add translation catalogs to GranteleeView", "empty QHash");
278  d->translationCatalogs.unite(catalogs);
279 }
280 
281 QVector<QLocale> CuteleeView::loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix, const QString &suffix)
282 {
283  QVector<QLocale> locales;
284 
285  if (Q_LIKELY(!filename.isEmpty() && !directory.isEmpty())) {
286  const QDir i18nDir(directory);
287  if (Q_LIKELY(i18nDir.exists())) {
288  const QString _prefix = prefix.isEmpty() ? QStringLiteral(".") : prefix;
289  const QString _suffix = suffix.isEmpty() ? QStringLiteral(".qm") : suffix;
290  const QStringList namesFilter = 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 = fn.mid(prefIdx + _prefix.length(), fn.length() - prefIdx - _suffix.length() - _prefix.length());
298  QLocale loc(locString);
299  if (Q_LIKELY(loc.language() != QLocale::C)) {
300  auto trans = new QTranslator(this);
301  if (Q_LIKELY(trans->load(loc, filename, _prefix, directory))) {
302  addTranslator(loc, trans);
303  locales.append(loc);
304  qCDebug(CUTELYST_CUTELEE) << "Loaded translations for locale" << loc << "from" << ts.absoluteFilePath();
305  } else {
306  delete trans;
307  qCWarning(CUTELYST_CUTELEE) << "Can not load translations for locale" << loc;
308  }
309  } else {
310  qCWarning(CUTELYST_CUTELEE) << "Can not load translations for invalid locale string" << locString;
311  }
312  }
313  locales.squeeze();
314  } else {
315  qCWarning(CUTELYST_CUTELEE) << "Can not find translation files for" << filename << "in directory" << directory;
316  }
317  } else {
318  qCWarning(CUTELYST_CUTELEE) << "Can not load translations from not existing directory:" << directory;
319  }
320  } else {
321  qCWarning(CUTELYST_CUTELEE) << "Can not load translations for empty file name or empty path.";
322  }
323 
324  return locales;
325 }
326 
327 #include "moc_cuteleeview.cpp"
Cutelyst::Context
The Cutelyst Context.
Definition: context.h:50
Cutelyst::CuteleeView::render
QByteArray render(Context *c) const final
Definition: cuteleeview.cpp:173
Cutelyst::Context::setStash
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:225
Cutelyst::CuteleeView::setIncludePaths
void setIncludePaths(const QStringList &paths)
Sets the list of include paths which will be looked for when resolving templates files.
Definition: cuteleeview.cpp:77
Cutelyst::Context::translate
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:473
Cutelyst::CuteleeView::CuteleeView
CuteleeView(QObject *parent=nullptr, const QString &name=QString())
Constructs a CuteleeView object with the given parent and name.
Definition: cuteleeview.cpp:38
Cutelyst::Response::setBody
void setBody(QIODevice *body)
Definition: response.cpp:114
Cutelyst::Context::error
bool error() const
Returns true if an error was set.
Definition: context.cpp:63
Cutelyst::Context::locale
QLocale locale() const
Definition: context.cpp:449
Cutelyst::CuteleeView::addTranslationCatalogs
void addTranslationCatalogs(const QHash< QString, QString > &catalogs)
Definition: cuteleeview.cpp:274
Cutelyst::View
Cutelyst View abstract view component
Definition: view.h:34
Cutelyst::CuteleeView::isCaching
bool isCaching() const
Returns true if caching is enabled.
Definition: cuteleeview.cpp:167
Cutelyst
The Cutelyst namespace holds all public Cutelyst API.
Definition: Mainpage.dox:7
Cutelyst::Component::name
QString name() const
Definition: component.cpp:44
Cutelyst::CuteleeView::addTranslationCatalog
void addTranslationCatalog(const QString &path, const QString &catalog)
Definition: cuteleeview.cpp:266
Cutelyst::CuteleeView::setWrapper
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:104
Cutelyst::CuteleeView::engine
Cutelee::Engine * engine() const
Definition: cuteleeview.cpp:132
Cutelyst::Context::res
Response * res() const
Definition: context.cpp:116
Cutelyst::CuteleeView::preloadTemplates
void preloadTemplates()
Definition: cuteleeview.cpp:138
Cutelyst::CuteleeView
Definition: cuteleeview.h:48
Cutelyst::Component::reverse
QString reverse() const
Definition: component.cpp:56
Cutelyst::CuteleeView::loadTranslationsFromDir
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
Definition: cuteleeview.cpp:281
Cutelyst::CuteleeView::setCache
void setCache(bool enable)
Sets if template caching should be done, this increases performance at the cost of higher memory usag...
Definition: cuteleeview.cpp:111
Cutelyst::CuteleeView::addTranslator
void addTranslator(const QLocale &locale, QTranslator *translator)
Definition: cuteleeview.cpp:254
Cutelyst::CuteleeView::setTemplateExtension
void setTemplateExtension(const QString &extension)
Sets the template extension, defaults to ".html".
Definition: cuteleeview.cpp:91
Cutelyst::Context::stash
void stash(const QVariantHash &unite)
Definition: context.h:558