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