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#ifdef __GNUC__
35#pragma GCC diagnostic push
36#pragma GCC diagnostic ignored "-Wsuggest-override"
37#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
38#endif
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#ifdef __GNUC__
54#pragma GCC diagnostic pop
55#endif
56
57
59
61 : m_manager(nullptr, nullptr, false,
62 new sword::EncodingFilterMgr(sword::ENC_UTF8), true)
63 , m_dataModel(BtBookshelfModel::newInstance())
64{
65 auto const clearCache =
66 [this]() noexcept {
68 decltype(m_availableLanguagesCache)(),
69 std::memory_order_relaxed);
70 };
71 BT_CONNECT(m_dataModel.get(), &BtBookshelfModel::rowsAboutToBeInserted,
72 clearCache);
73 BT_CONNECT(m_dataModel.get(), &BtBookshelfModel::rowsAboutToBeRemoved,
74 clearCache);
75
79
81 m_instance = this;
82}
83
84CSwordBackend::CSwordBackend(const QString & path, const bool augmentHome)
85 : m_manager(!path.isEmpty() ? path.toLocal8Bit().constData() : nullptr,
86 false, new sword::EncodingFilterMgr(sword::ENC_UTF8),
87 false, augmentHome)
88 , m_dataModel(BtBookshelfModel::newInstance())
89{}
90
94
96
97 for (CSwordModuleInfo * const m : moduleList())
98 if(m->type() == type)
99 return m;
100 return nullptr;
101
102}
103
104std::shared_ptr<CSwordBackend::AvailableLanguagesCacheContainer const>
106 auto oldCache = m_availableLanguagesCache.load(std::memory_order_acquire);
107 if (oldCache)
108 return oldCache;
109
110 auto const generateCache =
111 [&model = *m_dataModel] {
113 for (auto const * const mod : model.moduleList()) {
114 newCache.emplace(mod->language());
115 if (auto lang2 = mod->glossaryTargetlanguage())
116 newCache.emplace(std::move(lang2));
117 }
118
119 return std::make_shared<AvailableLanguagesCacheContainer const>(
120 std::move(newCache)); // also makes container const
121 };
122
123 for (auto newCache = generateCache();; newCache = generateCache())
124 if (m_availableLanguagesCache.compare_exchange_strong(
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 // Get the set of sword directories that could contain modules:
383 QSet<QString> swordDirSet;
384 QList<QFileInfo> configs;
385
386 auto const userHomeSwordDir = util::directory::getUserHomeSwordDir();
387 if (auto conf = QFileInfo(userHomeSwordDir, QStringLiteral("sword.conf"));
388 conf.exists())
389 {
390 // Use the private sword.conf file:
391 configs << std::move(conf);
392 } else {
393 /*
394 Did not find private sword.conf, will use shared sword.conf files to
395 build the private one. Once the private sword.conf exist, the shared
396 ones will not be searched again.
397 */
398 #ifdef Q_OS_WIN
399 auto const swordPath = qEnvironmentVariable("SWORD_PATH");
400 for (auto const & path : swordPath.split(QDir::listSeparator())) {
401 QDir dir(path);
402
403 /*
404 On Windows, add the shared sword directory to the set so the new
405 private sword.conf will have it. The user could decide to delete this
406 shared path and it will not automatically come back.
407 */
408 swordDirSet << dir.absolutePath();
409
410 // %ProgramData%\Sword\sword.conf
411 if (auto conf = QFileInfo(dir, QStringLiteral("Sword/sword.conf"));
412 conf.exists())
413 configs << std::move(conf);
414 }
415
416 #else
417 // /etc/sword.conf, /usr/local/etc/sword.conf
418 for (auto const & path : QString(m_manager.globalConfPath).split(':'))
419 if (auto conf = QFileInfo(path); conf.exists())
420 configs << std::move(conf);
421 #endif
422 }
423
424 // Search the sword.conf file(s) for sword directories that could contain modules
425 static constexpr auto const decoderFlags = QStringDecoder::Flag::Stateless;
426 for (auto const & fileInfo : configs) {
427 /*
428 Get all DataPath and AugmentPath entries from the config file and add
429 them to the list:
430 */
431 sword::SWConfig conf(fileInfo.absoluteFilePath().toUtf8().constData());
432
433 static auto const decodeConfEntry =
434 [](sword::SWBuf const & buf) {
435 QStringDecoder utf8Decoder("UTF-8", decoderFlags);
436 // Do not use auto here due to QTBUG-117705/QTBUG-117902:
437 QString result = utf8Decoder(buf.c_str());
438 if (!utf8Decoder.hasError())
439 return result;
440 QStringDecoder cp1252Decoder("Windows-1252", decoderFlags);
441 result = cp1252Decoder(buf.c_str());
442 if (!cp1252Decoder.hasError())
443 return result;
444 return QString::fromLatin1(buf.c_str());
445 };
446
447 swordDirSet << QDir(decodeConfEntry(conf["Install"]["DataPath"])).absolutePath();
448
449 const sword::ConfigEntMap group(conf["Install"]);
450 for (auto its = group.equal_range("AugmentPath");
451 its.first != its.second;
452 ++(its.first))
453 {
454 // Added augment path:
455 swordDirSet << QDir(decodeConfEntry(its.first->second)).absolutePath();
456 }
457 }
458
459 // Add the private sword path to the set if not there already:
460 swordDirSet << userHomeSwordDir.absolutePath();
461
462 QStringList swordDirs;
463 for (auto dir: swordDirSet)
464 swordDirs.append(dir);
465 return swordDirs;
466}
467
469 const QStringList entries = QDir(CSwordModuleInfo::getGlobalBaseIndexLocation()).entryList(QDir::Dirs);
470 for (auto const & entry : entries) {
471 if (entry == '.' || entry == QStringLiteral(".."))
472 continue;
473 if (CSwordModuleInfo * const module = findModuleByName(entry)) {
474 if (!module->hasIndex()) { //index files found, but wrong version etc.
475 qDebug() << "deleting outdated index for module" << entry;
477 }
478 } else { //no module exists
479 if (btConfig().value<bool>(
480 QStringLiteral(
481 "settings/behaviour/autoDeleteOrphanedIndices"), true))
482 {
483 qDebug() << "deleting orphaned index in directory" << entry;
485 }
486 }
487 }
488}
#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