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