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