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