Cutelyst  2.14.2
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 
78 {
79  Q_D(CuteleeView);
80  d->loader->setTemplateDirs(paths);
81  d->includePaths = paths;
82  Q_EMIT changed();
83 }
84 
86 {
87  Q_D(const CuteleeView);
88  return d->extension;
89 }
90 
92 {
93  Q_D(CuteleeView);
94  d->extension = extension;
95  Q_EMIT changed();
96 }
97 
99 {
100  Q_D(const CuteleeView);
101  return d->wrapper;
102 }
103 
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 
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 
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 
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"
bool empty() const
int indexOf(QChar ch, int from, Qt::CaseSensitivity cs) const
Response * res() const
Definition: context.cpp:116
QSharedPointer< T > create()
QString name() const
Definition: component.cpp:44
void append(const T &value)
void addTranslator(const QLocale &locale, QTranslator *translator)
void setStash(const QString &key, const QVariant &value)
Definition: context.cpp:225
QString & remove(int position, int n)
QString currentPath()
bool error() const
Returns true if an error was set.
Definition: context.cpp:63
QByteArray render(Context *c) const final
The Cutelyst Context.
Definition: context.h:51
QString fromLocal8Bit(const char *str, int size)
bool exists() const
QFileInfoList entryInfoList(Filters filters, SortFlags sort) const
bool isEmpty() const
bool startsWith(const QString &s, Qt::CaseSensitivity cs) const
Language language() const
void setIncludePaths(const QStringList &paths)
Sets the list of include paths which will be looked for when resolving templates files.
Definition: cuteleeview.cpp:77
QVariant config(const QString &key, const QVariant &defaultValue=QVariant()) const
void squeeze()
void reserve(int size)
QVariant fromValue(const T &value)
QLocale locale() const
Definition: context.cpp:457
void addTranslationCatalogs(const QHash< QString, QString > &catalogs)
void addTranslationCatalog(const QString &path, const QString &catalog)
QString mid(int position, int n) const
QString templateExtension() const
Returns the template extension.
QString translate(const char *context, const char *sourceText, const char *disambiguation=nullptr, int n=-1) const
Definition: context.cpp:481
QString wrapper() const
Returns the template wrapper.
void setCache(bool enable)
Sets if template caching should be done, this increases performance at the cost of higher memory usag...
bool isCaching() const
Returns true if caching is enabled.
Cutelyst View abstract view component
Definition: view.h:34
int length() const
QString reverse() const
Definition: component.cpp:56
The Cutelyst Application.
Definition: application.h:55
void setBody(QIODevice *body)
Definition: response.cpp:114
Cutelee::Engine * engine() const
QString toString() const
void setTemplateExtension(const QString &extension)
Sets the template extension, defaults to ".html".
Definition: cuteleeview.cpp:91
void setWrapper(const QString &name)
Sets the template wrapper name, the template will be rendered into content variable in which the wrap...
QStringList includePaths() const
Returns the list of include paths.
QVector< QLocale > loadTranslationsFromDir(const QString &filename, const QString &directory, const QString &prefix=QStringLiteral("."), const QString &suffix=QStringLiteral(".qm"))
void stash(const QVariantHash &unite)
Definition: context.h:568
QByteArray toUtf8() const