BibleTime
cswordbackend.cpp
Go to the documentation of this file.
1 /*********
2 *
3 * In the name of the Father, and of the Son, and of the Holy Spirit.
4 *
5 * This file is part of BibleTime's source code, https://bibletime.info/
6 *
7 * Copyright 1999-2021 by the BibleTime developers.
8 * The BibleTime source code is licensed under the GNU General Public License
9 * version 2.0.
10 *
11 **********/
12 
13 #include "cswordbackend.h"
14 
15 #include <QDebug>
16 #include <QDir>
17 #include <QFileInfo>
18 #include <QSet>
19 #include <QString>
20 #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
21 #include <QStringDecoder>
22 #endif
23 #include <string_view>
24 #include "../../util/btconnect.h"
25 #include "../../util/directory.h"
26 #include "../btglobal.h"
27 #include "../btinstallmgr.h"
28 #include "../config/btconfig.h"
29 #include "../drivers/cswordbiblemoduleinfo.h"
30 #include "../drivers/cswordbookmoduleinfo.h"
31 #include "../drivers/cswordcommentarymoduleinfo.h"
32 #include "../drivers/cswordlexiconmoduleinfo.h"
33 #include "btlocalemgr.h"
34 
35 // Sword includes:
36 #pragma GCC diagnostic push
37 #pragma GCC diagnostic ignored "-Wsuggest-override"
38 #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
39 #ifdef __clang__
40 #pragma clang diagnostic push
41 #pragma clang diagnostic ignored "-Wsuggest-destructor-override"
42 #endif
43 #include <encfiltmgr.h>
44 #include <filemgr.h>
45 #include <rtfhtml.h>
46 #include <swfiltermgr.h>
47 #include <swfilter.h>
48 #include <swversion.h>
49 #include <utilstr.h>
50 #ifdef __clang__
51 #pragma clang diagnostic pop
52 #endif
53 #pragma GCC diagnostic pop
54 
55 
57 
59  : m_manager(nullptr, nullptr, false,
60  new sword::EncodingFilterMgr(sword::ENC_UTF8), true)
61  , m_dataModel(BtBookshelfModel::newInstance())
62 {
63  auto const clearCache =
64  [this]() noexcept {
65  std::atomic_store_explicit(
67  decltype(m_availableLanguagesCache)(),
68  std::memory_order_relaxed);
69  };
70  BT_CONNECT(m_dataModel.get(), &BtBookshelfModel::rowsAboutToBeInserted,
71  clearCache);
72  BT_CONNECT(m_dataModel.get(), &BtBookshelfModel::rowsAboutToBeRemoved,
73  clearCache);
74 
76  initModules();
78 
80  m_instance = this;
81 }
82 
83 CSwordBackend::CSwordBackend(const QString & path, const bool augmentHome)
84  : m_manager(!path.isEmpty() ? path.toLocal8Bit().constData() : nullptr,
85  false, new sword::EncodingFilterMgr(sword::ENC_UTF8),
86  false, augmentHome)
87  , m_dataModel(BtBookshelfModel::newInstance())
88 {}
89 
92 }
93 
95 
96  for (CSwordModuleInfo * const m : moduleList())
97  if(m->type() == type)
98  return m;
99  return nullptr;
100 
101 }
102 
103 std::shared_ptr<CSwordBackend::AvailableLanguagesCacheContainer const>
105 {
106  auto oldCache = std::atomic_load_explicit(&m_availableLanguagesCache,
107  std::memory_order_acquire);
108  if (oldCache)
109  return oldCache;
110 
111  auto const generateCache =
112  [&model = *m_dataModel] {
114  for (auto const * const mod : model.moduleList()) {
115  newCache.emplace(mod->language());
116  if (auto lang2 = mod->glossaryTargetlanguage())
117  newCache.emplace(std::move(lang2));
118  }
119 
120  return std::make_shared<AvailableLanguagesCacheContainer const>(
121  std::move(newCache)); // also makes container const
122  };
123 
124  for (auto newCache = generateCache();; newCache = generateCache())
125  if (std::atomic_compare_exchange_strong_explicit(
127  &oldCache,
128  newCache,
129  std::memory_order_acq_rel,
130  std::memory_order_relaxed))
131  return newCache;
132 }
133 
135  if (toBeDeleted.empty())
136  return;
137  m_dataModel->removeModules(toBeDeleted);
138  Q_EMIT sigSwordSetupChanged();
139 
140  BtInstallMgr installMgr;
141  QMap<QString, sword::SWMgr *> mgrDict; // Maps config paths to SWMgr objects
142  for (CSwordModuleInfo const * const mInfo : toBeDeleted) {
143  // Find the install path for the sword manager:
144  QString dataPath = mInfo->config(CSwordModuleInfo::DataPath);
145  if (dataPath.left(2) == QStringLiteral("./"))
146  dataPath = dataPath.mid(2);
147 
148  QString prefixPath =
149  mInfo->config(CSwordModuleInfo::AbsoluteDataPath) + '/';
150  if (prefixPath.contains(dataPath)) {
151  // Remove module part to get the prefix path:
152  prefixPath = prefixPath.remove(prefixPath.indexOf(dataPath),
153  dataPath.length());
154  } else { // This is an error, should not happen
155  qWarning() << "Removing" << mInfo->name()
156  << "didn't succeed because the absolute path"
157  << prefixPath << "didn't contain the data path"
158  << dataPath;
159  continue; // don't remove this, go to next of the for loop
160  }
161 
162  // Create the sword manager and remove the module
163  sword::SWMgr * mgr = mgrDict[prefixPath];
164  if (!mgr) { // Create new mgr if it's not yet available
165  mgrDict.insert(prefixPath,
166  new sword::SWMgr(prefixPath.toLocal8Bit()));
167  mgr = mgrDict[prefixPath];
168  }
169  qDebug() << "Removing the module" << mInfo->name() << "...";
170  installMgr.removeModule(mgr, mInfo->swordModule().getName());
171  }
172  qDeleteAll(toBeDeleted);
173  qDeleteAll(mgrDict);
174  mgrDict.clear();
175 }
176 
179  for (auto const & name : names)
180  if (CSwordModuleInfo * const mInfo = findModuleByName(name))
181  list.append(mInfo);
182  return list;
183 }
184 
185 BtConstModuleList CSwordBackend::getConstPointerList(const QStringList & names) const {
186  BtConstModuleList list;
187  for (auto const & name : names)
188  if (CSwordModuleInfo const * const mInfo = findModuleByName(name))
189  list.append(mInfo);
190  return list;
191 }
192 
194  // qWarning("globalSwordConfigPath is %s", globalConfPath);
195 
196  shutdownModules(); // Remove previous modules
197  m_dataModel->clear();
198 
199  const LoadError ret = static_cast<LoadError>(m_manager.load());
200 
201  for (auto const & modulePair : m_manager.getModules()) {
202  sword::SWModule * const curMod = modulePair.second;
203  BT_ASSERT(curMod);
204  std::unique_ptr<CSwordModuleInfo> newModule;
205 
206  std::string_view const modType = curMod->getType();
207  using namespace std::literals;
208  if (modType == "Biblical Texts"sv) {
209  newModule = std::make_unique<CSwordBibleModuleInfo>(*curMod, *this);
210  } else if (modType == "Commentaries"sv) {
211  newModule = std::make_unique<CSwordCommentaryModuleInfo>(*curMod,
212  *this);
213  } else if (modType == "Lexicons / Dictionaries"sv) {
214  newModule = std::make_unique<CSwordLexiconModuleInfo>(*curMod,
215  *this);
216  } else if (modType == "Generic Books"sv) {
217  newModule = std::make_unique<CSwordBookModuleInfo>(*curMod, *this);
218  } else {
219  continue;
220  }
221 
222  // Append the new modules to our list, but only if it's supported
223  // The constructor of CSwordModuleInfo prints a warning on stdout
224  if (!newModule->hasVersion()
225  || (newModule->minimumSwordVersion() <= sword::SWVersion::currentVersion))
226  {
227 
228  /* There is currently a deficiency in sword 1.8.1 in that backend->setCipherKey() does
229  * not work correctly for modules from which data was already fetched. Therefore we have to
230  * reload the modules. The cipher key must be set before any read occurs on the module.
231  * Reading from the module can happen in subtle ways. The addModule below causes a read
232  * to determine if the locked or unlocked icon is used by the model.
233  */
234  if (newModule->isEncrypted()) {
235  auto const unlockKey(
236  btConfig().getModuleEncryptionKey(newModule->name()));
237  if (!unlockKey.isNull())
238  m_manager.setCipherKey(
239  newModule->name().toUtf8().constData(),
240  unlockKey.toUtf8().constData());
241  }
242 
243  /// \todo Refactor data model to use shared_ptr to contain works
244  m_dataModel->addModule(newModule.get());
245  newModule.release();
246  }
247  }
248 
249  Q_EMIT sigSwordSetupChanged();
250  return ret;
251 }
252 
253 void CSwordBackend::Private::addRenderFilters(sword::SWModule * module,
254  sword::ConfigEntMap & section)
255 {
256  auto entry(section.find("SourceType"));
257  if (entry != section.end()) {
258  if (entry->second == "OSIS") {
259  module->addRenderFilter(&m_osisFilter);
260  return;
261  } else if (entry->second == "ThML") {
262  module->addRenderFilter(&m_thmlFilter);
263  return;
264  } else if (entry->second == "TEI") {
265  module->addRenderFilter(&m_teiFilter);
266  return;
267  } else if (entry->second == "GBF") {
268  module->addRenderFilter(&m_gbfFilter);
269  return;
270  } else if (entry->second == "PLAIN") {
271  module->addRenderFilter(&m_plainFilter);
272  return;
273  }
274  }
275 
276  // No driver found
277  entry = section.find("ModDrv");
278  if (entry != section.end() && (entry->second == "RawCom" || entry->second == "RawLD"))
279  module->addRenderFilter(&m_plainFilter);
280 }
281 
283  m_dataModel->clear(true);
285 }
286 
288  //BT mods are deleted now, delete Sword mods, too.
289  deleteAllModules();
290 
291  /* Cipher filters must be handled specially, because SWMgr creates them,
292  * stores them in cipherFilters and cleanupFilters and attaches them to locked
293  * modules. If these modules are removed, the filters need to be removed as well,
294  * so that they are re-created for the new module objects.
295  */
296  for (auto const & filterPair : cipherFilters) {
297  //Delete the Filter and remove it from the cleanup list
298  cleanupFilters.remove(filterPair.second);
299  delete filterPair.second;
300  }
301  cipherFilters.clear();
302 }
303 
305  const int state)
306 { m_manager.setGlobalOption(option.optionName, option.valueToString(state)); }
307 
320  // setOption(CSwordModuleInfo::transliteration, options.transliteration);
322 }
323 
324 CSwordModuleInfo * CSwordBackend::findModuleByName(const QString & name) const {
325  for (auto * const mod : m_dataModel->moduleList())
326  if (mod->name().compare(name, Qt::CaseInsensitive) == 0)
327  return mod;
328  return nullptr;
329 }
330 
331 CSwordModuleInfo * CSwordBackend::findSwordModuleByPointer(const sword::SWModule * const swmodule) const {
332  for (auto * const mod : m_dataModel->moduleList())
333  if (&mod->swordModule() == swmodule)
334  return mod;
335  return nullptr;
336 }
337 
339 { return BtLocaleMgr::defaultLocaleName(); }
340 
341 void CSwordBackend::setBooknameLanguage(QString const & language) {
343 
344  /* Use locale name returned by SWORD instead of using the given argument
345  directly, as the language may be different. */
346  auto const newLocaleName = BtLocaleMgr::defaultLocaleName().toUtf8();
347 
348  /* Refresh the locale of all Bible and commentary modules. Whereas the
349  locale is only used by modules with verse keys (i.e. sword::VerseKey) and
350  ignored on all other modules, we could just set the locale on those
351  modules (bibles and commentaries) only. However, since the keys are used
352  for all uncountable abominations, it feels safer to set the locale on all
353  modules, lest an incorrect locale gets copied from a non-VerseKey module
354  to modules with verse keys. This has the slight overhead of copying the
355  locale name for each such module, but this is hopefully negligible due to
356  this method not being called very often. */
357  for (auto const * const mod : m_dataModel->moduleList())
358  mod->swordModule().getKey()->setLocale(newLocaleName.constData());
359 }
360 
362  shutdownModules();
364  initModules();
365 }
366 
368  //delete Sword's config to make Sword reload it!
369 
370  if (myconfig) { // force reload on config object because we may have changed the paths
371  delete myconfig;
372  config = myconfig = nullptr;
373  // we need to call findConfig to make sure that augPaths are reloaded
374  findConfig(&configType, &prefixPath, &configPath, &augPaths, &sysConfig);
375  // now re-read module configuration files
376  loadConfigDir(configPath);
377  } else if (config) {
378  config->load();
379  }
380 }
381 
382 // Return a list of used Sword dirs. Useful for the installer.
383 QStringList CSwordBackend::swordDirList() const {
384  namespace DU = util::directory;
385 
386  // Get the set of sword directories that could contain modules:
387  QSet<QString> swordDirSet;
388  QList<QFileInfo> configs;
389 
390  auto const userHomeSwordDir = util::directory::getUserHomeSwordDir();
391  if (auto conf = QFileInfo(userHomeSwordDir, QStringLiteral("sword.conf"));
392  conf.exists())
393  {
394  // Use the private sword.conf file:
395  configs << std::move(conf);
396  } else {
397  /*
398  Did not find private sword.conf, will use shared sword.conf files to
399  build the private one. Once the private sword.conf exist, the shared
400  ones will not be searched again.
401  */
402  #ifdef Q_OS_WIN
403  auto const swordPath = qEnvironmentVariable("SWORD_PATH");
404  for (auto const & path : swordPath.split(QDir::listSeparator())) {
405  QDir dir(path);
406 
407  /*
408  On Windows, add the shared sword directory to the set so the new
409  private sword.conf will have it. The user could decide to delete this
410  shared path and it will not automatically come back.
411  */
412  swordDirSet << dir.absolutePath();
413 
414  // %ProgramData%\Sword\sword.conf
415  if (auto conf = QFileInfo(dir, QStringLiteral("Sword/sword.conf"));
416  conf.exists())
417  configs << std::move(conf);
418  }
419 
420  #else
421  // /etc/sword.conf, /usr/local/etc/sword.conf
422  for (auto const & path : QString(m_manager.globalConfPath).split(':'))
423  if (auto conf = QFileInfo(path); conf.exists())
424  configs << std::move(conf);
425  #endif
426  }
427 
428  // Search the sword.conf file(s) for sword directories that could contain modules
429  #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
430  static constexpr auto const decoderFlags = QStringDecoder::Flag::Stateless;
431  #endif
432  for (auto const & fileInfo : configs) {
433  /*
434  Get all DataPath and AugmentPath entries from the config file and add
435  them to the list:
436  */
437  sword::SWConfig conf(fileInfo.absoluteFilePath().toUtf8().constData());
438 
439  static auto const decodeConfEntry =
440  [](sword::SWBuf const & buf) {
441  #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
442  return QString::fromUtf8(buf.c_str());
443  #else
444  QStringDecoder utf8Decoder("UTF-8", decoderFlags);
445  // Do not use auto here due to QTBUG-117705/QTBUG-117902:
446  QString result = utf8Decoder(buf.c_str());
447  if (!utf8Decoder.hasError())
448  return result;
449  QStringDecoder cp1252Decoder("Windows-1252", decoderFlags);
450  result = cp1252Decoder(buf.c_str());
451  if (!cp1252Decoder.hasError())
452  return result;
453  return QString::fromLatin1(buf.c_str());
454  #endif
455  };
456 
457  swordDirSet << QDir(decodeConfEntry(conf["Install"]["DataPath"])).absolutePath();
458 
459  const sword::ConfigEntMap group(conf["Install"]);
460  for (auto its = group.equal_range("AugmentPath");
461  its.first != its.second;
462  ++(its.first))
463  {
464  // Added augment path:
465  swordDirSet << QDir(decodeConfEntry(its.first->second)).absolutePath();
466  }
467  }
468 
469  // Add the private sword path to the set if not there already:
470  swordDirSet << userHomeSwordDir.absolutePath();
471 
472  QStringList swordDirs;
473  for (auto dir: swordDirSet)
474  swordDirs.append(dir);
475  return swordDirs;
476 }
477 
479  const QStringList entries = QDir(CSwordModuleInfo::getGlobalBaseIndexLocation()).entryList(QDir::Dirs);
480  for (auto const & entry : entries) {
481  if (entry == '.' || entry == QStringLiteral(".."))
482  continue;
483  if (CSwordModuleInfo * const module = findModuleByName(entry)) {
484  if (!module->hasIndex()) { //index files found, but wrong version etc.
485  qDebug() << "deleting outdated index for module" << entry;
487  }
488  } else { //no module exists
489  if (btConfig().value<bool>(
490  QStringLiteral(
491  "settings/behaviour/autoDeleteOrphanedIndices"), true))
492  {
493  qDebug() << "deleting orphaned index in directory" << entry;
495  }
496  }
497  }
498 }
#define BT_ASSERT(...)
Definition: btassert.h:17
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition: btconfig.h:305
#define BT_CONNECT(...)
Definition: btconnect.h:20
QList< CSwordModuleInfo const * > BtConstModuleList
Definition: btmodulelist.h:21
The backend layer main class, a backend implementation of Sword.
Definition: cswordbackend.h:56
void shutdownModules()
Deinitializes and frees the modules.
void setOption(CSwordModuleInfo::FilterOption const &type, const int state)
Sets the state of the given filter option.
std::shared_ptr< AvailableLanguagesCacheContainer const > availableLanguages() noexcept
CSwordBackend::LoadError initModules()
Initializes the Sword modules.
QStringList swordDirList() const
Sword prefix list.
std::set< std::shared_ptr< Language const > > AvailableLanguagesCacheContainer
Definition: cswordbackend.h:75
static CSwordBackend * m_instance
void reloadModules()
Reloads all Sword modules.
std::shared_ptr< BtBookshelfModel > const m_dataModel
QList< CSwordModuleInfo * > getPointerList(const QStringList &names) const
CSwordModuleInfo * findFirstAvailableModule(CSwordModuleInfo::ModuleType type)
CSwordBackend::Private m_manager
void deleteOrphanedIndices()
QList< CSwordModuleInfo * > const & moduleList() const
CSwordModuleInfo * findModuleByName(const QString &name) const
Searches for a module with the given name.
QString prefixPath() const
void uninstallModules(BtConstModuleSet const &modules)
Uninstalls the given modules.
void sigSwordSetupChanged()
CSwordBackend()
Creates the regular CSwordBackend instance.
BtConstModuleList getConstPointerList(const QStringList &names) const
LoadError
The error codes which may be returned by the Load() call.
Definition: cswordbackend.h:66
void setFilterOptions(const FilterOptions &options)
CSwordModuleInfo * findSwordModuleByPointer(const sword::SWModule *const swmodule) const
Searches for a module with the given sword module as module().
~CSwordBackend() override
void setBooknameLanguage(QString const &langName)
Sets the language for the international booknames of Sword.
std::shared_ptr< AvailableLanguagesCacheContainer const > m_availableLanguagesCache
std::shared_ptr< BtBookshelfModel > model()
QString booknameLanguage() const
static FilterOption const redLetterWords
static FilterOption const headings
static FilterOption const textualVariants
static void deleteIndexForModule(const QString &name)
static FilterOption const hebrewPoints
static QString getGlobalBaseIndexLocation()
static FilterOption const strongNumbers
static FilterOption const morphTags
static FilterOption const scriptureReferences
static FilterOption const hebrewCantillation
static FilterOption const morphSegmentation
static FilterOption const greekAccents
static FilterOption const footnotes
static FilterOption const lemmas
QString defaultLocaleName()
Definition: btlocalemgr.cpp:57
void setDefaultLocaleName(QString const &localeName)
Definition: btlocalemgr.cpp:60
const QDir & getUserHomeSwordDir()
Definition: directory.cpp:342
void addRenderFilters(sword::SWModule *module, sword::ConfigEntMap &section) override
char const *(* valueToString)(int value) noexcept
int morphSegmentation
Definition: btglobal.h:37
int hebrewCantillation
Definition: btglobal.h:32
int morphTags
Definition: btglobal.h:29
int textualVariants
Definition: btglobal.h:34
int hebrewPoints
Definition: btglobal.h:31
int footnotes
Definition: btglobal.h:26
int scriptureReferences
Definition: btglobal.h:36
int greekAccents
Definition: btglobal.h:33
int headings
Definition: btglobal.h:28
int redLetterWords
Definition: btglobal.h:35
int strongNumbers
Definition: btglobal.h:27