BibleTime
cswordmoduleinfo.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 "cswordmoduleinfo.h"
14
15#include <memory>
16#include <cassert>
17#include <CLucene.h>
18#include <optional>
19#include <QByteArray>
20#include <QCoreApplication>
21#include <QDebug>
22#include <QDir>
23#include <QFileInfo>
24#include <QLocale>
25#include <QScopeGuard>
26#include <QSettings>
27#include <QTextDocument>
28#include <string>
29#include <string_view>
30#include <type_traits>
31#include <utility>
32#include "../../util/btassert.h"
33#include "../../util/cresmgr.h"
34#include "../../util/directory.h"
35#include "../../util/tool.h"
36#include "../config/btconfig.h"
37#include "../keys/cswordkey.h"
38#include "../managers/cswordbackend.h"
39#include "../cswordmodulesearch.h"
42
43// Sword includes:
44#ifdef __GNUC__
45#pragma GCC diagnostic push
46#pragma GCC diagnostic ignored "-Wsuggest-override"
47#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"
48#endif
49#include <listkey.h>
50#include <swbuf.h>
51#include <swconfig.h>
52#include <swkey.h>
53#include <swmodule.h>
54#include <swversion.h>
55#include <rtfhtml.h>
56#include <versekey.h>
57#ifdef __GNUC__
58#pragma GCC diagnostic pop
59#endif
60
61
62//Increment this, if the index format changes
63//Then indices on the user's systems will be rebuilt
64constexpr static unsigned const INDEX_VERSION = 7;
65
66//Maximum index entry size, 1MiB for now
67//Lucene default is too small
68constexpr static unsigned long const BT_MAX_LUCENE_FIELD_LENGTH = 1024 * 1024;
69
70namespace {
71
74 CSwordModuleInfo::Features const features,
75 sword::SWModule & module)
76{
77 /// \todo Maybe we can use raw string comparsion instead of QString?
78 QString const cat(module.getConfigEntry("Category"));
79
80 // Category has to be checked before type:
81 if (cat == QStringLiteral("Cults / Unorthodox / Questionable Material")) {
83 } else if (cat == QStringLiteral("Daily Devotional")
84 || features.testFlag(CSwordModuleInfo::FeatureDailyDevotion))
85 {
87 } else if (cat == QStringLiteral("Glossaries")
88 || features.testFlag(CSwordModuleInfo::FeatureGlossary))
89 {
91 } else if (cat == QStringLiteral("Images")
92 || cat == QStringLiteral("Maps"))
93 {
95 } else {
96 switch (type) {
103 default:
106 }
107 }
108}
109
110inline CSwordModuleInfo::Features retrieveFeatures(
111 sword::SWModule const & module)
112{
113 // See https://wiki.crosswire.org/DevTools:conf_Files
114 CSwordModuleInfo::Features features;
115 for (auto featureEntries = module.getConfig().equal_range("Feature");
116 featureEntries.first != featureEntries.second;
117 ++featureEntries.first)
118 {
119 #define T(f) \
120 if (featureEntries.first->second == #f) { \
121 features |= CSwordModuleInfo::Feature ## f; \
122 }
123 T(StrongsNumbers)
124 else T(GreekDef)
125 else T(HebrewDef)
126 else T(GreekParse)
127 else T(HebrewParse)
128 else T(DailyDevotion)
129 else T(Glossary)
130 else T(Images)
131 else T(NoParagraphs)
132 #undef T
133 }
134 return features;
135}
136
137static const TCHAR * stop_words[] = { nullptr };
138
139class Analyzer : public lucene::analysis::standard::StandardAnalyzer {
140
141public: // Methods:
142
143 Analyzer() : lucene::analysis::standard::StandardAnalyzer(stop_words) {}
144
145};
146
147} // anonymous namespace
148
149char const *
151{ return value ? "On" : "Off"; }
152
153char const *
155 switch (value) {
156 case 0: return "Primary Reading";
157 case 1: return "Secondary Reading";
158 default: return "All Readings";
159 }
160}
161
163 "Footnotes",
164 "Footnotes",
165 QT_TRANSLATE_NOOP("QObject", "Footnotes")};
166
168 "Strong's Numbers",
169 "Strongs",
170 QT_TRANSLATE_NOOP("QObject", "Strong's numbers")};
171
173 "Headings",
174 "Headings",
175 QT_TRANSLATE_NOOP("QObject", "Headings")};
176
178 "Morphological Tags",
179 "Morph",
180 QT_TRANSLATE_NOOP("QObject", "Morphological tags")};
181
183 "Lemmas",
184 "Lemma",
185 QT_TRANSLATE_NOOP("QObject", "Lemmas")};
186
188 "Hebrew Vowel Points",
189 "HebrewPoints",
190 QT_TRANSLATE_NOOP("QObject", "Hebrew vowel points")};
191
193 "Hebrew Cantillation",
194 "Cantillation",
195 QT_TRANSLATE_NOOP("QObject", "Hebrew cantillation marks")};
196
198 "Greek Accents",
199 "GreekAccents",
200 QT_TRANSLATE_NOOP("QObject", "Greek accents")};
201
203 "Cross-references",
204 "Scripref",
205 QT_TRANSLATE_NOOP("QObject", "Scripture cross-references")};
206
208 "Words of Christ in Red",
209 "RedLetterWords",
210 QT_TRANSLATE_NOOP("QObject", "Red letter words")};
211
213 "Textual Variants",
214 "Variants",
215 QT_TRANSLATE_NOOP("QObject", "Textual variants"),
217
219 "Morph Segmentation",
220 "MorphSegmentation",
221 QT_TRANSLATE_NOOP("QObject", "Morph segmentation")};
222
223CSwordModuleInfo::CSwordModuleInfo(sword::SWModule & module,
224 CSwordBackend & backend,
225 ModuleType type)
226 : m_swordModule(module)
227 , m_backend(backend)
228 , m_type(type)
229 , m_cancelIndexing(false)
230 , m_cachedName(QString::fromUtf8(module.getName()))
231 , m_cachedFeatures(retrieveFeatures(module))
232 , m_cachedCategory(retrieveCategory(type, m_cachedFeatures, module))
233 , m_cachedLanguage(
234 Language::fromAbbrev(
235 util::tool::fixSwordBcp47(
236 m_cachedCategory == Category::Glossaries
237 /* Special handling for glossaries, we use the "from language" as
238 language for the module: */
239 ? config(GlossaryFrom)
240 : module.getLanguage())))
241 , m_cachedGlossaryTargetLanguage(
242 m_cachedCategory == Category::Glossaries
243 ? Language::fromAbbrev(
244 util::tool::fixSwordBcp47(module.getLanguage()))
245 : std::shared_ptr<Language const>())
246 , m_cachedHasVersion(
247 ((*m_backend.getConfig())[module.getName()]["Version"]).size() > 0)
248{
249 m_hidden = btConfig().value<QStringList>(
250 QStringLiteral("state/hiddenModules"))
251 .contains(m_cachedName);
252
254 && (minimumSwordVersion() > sword::SWVersion::currentVersion))
255 {
256 qWarning("The module \"%s\" requires a newer Sword library. Please "
257 "update to \"Sword %s\".",
258 m_cachedName.toUtf8().constData(),
259 minimumSwordVersion().getText());
260
261 /// \todo if this is the case, can we use the module at all?
262 }
263}
264
265bool CSwordModuleInfo::unlock(const QString & unlockKey) {
266 if (!isEncrypted())
267 return false;
268
269 bool unlocked = unlockKeyIsValid();
270
272
273 /// \todo remove this comment once it is no longer needed
274 /* There is currently a deficiency in sword 1.8.1 in that
275 backend->setCipherKey() does not work correctly for modules from which
276 data was already fetched. Therefore we have to reload the modules in
277 bibletime.cpp */
278 m_backend.raw().setCipherKey(m_swordModule.getName(),
279 unlockKey.toUtf8().constData());
280
281 /// \todo write to Sword config as well
282
283 if (unlockKeyIsValid() != unlocked)
284 Q_EMIT unlockedChanged(!unlocked);
285 return true;
286}
287
289 // still works, but the cipherkey is stored in BtConfig.
290 // Works because it is set in sword on program startup.
291
292 return isEncrypted() && !unlockKeyIsValid();
293}
294
296 /**
297 * If we have the CipherKey entry the module
298 * is encrypted but not necessarily locked
299 */
300
301 /* This code is still right, though we do no longer write to the module
302 config files any more. */
303 auto const & sections = m_backend.getConfig()->getSections();
304 auto const it(sections.find(m_cachedName.toUtf8().constData()));
305 if (it == sections.end())
306 return false;
307
308 const sword::ConfigEntMap & config = it->second;
309 return config.find("CipherKey") != config.end();
310}
311
313 sword::SWKey * const key = m_swordModule.getKey();
314 sword::VerseKey * const vk = dynamic_cast<sword::VerseKey *>(key);
315 if (vk)
316 vk->setIntros(false);
317 m_swordModule.setPosition(sword::TOP);
318
319 /* This needs to use ::fromLatin1 because if the text is still locked, a lot
320 of garbage will show up. It will also work with properly decrypted
321 Unicode text, because all non-ASCII Unicode chars consist of bytes >127
322 and therefore contain no control (nonprintable) characters, which are all
323 <127. */
324 const QString test(isUnicode()
325 ? QString::fromUtf8(m_swordModule.getRawEntry())
326 : QString::fromLatin1(m_swordModule.getRawEntry()));
327
328 if (test.isEmpty())
329 return false;
330
331 for (int i = 0; i < test.length() && i < 100; i++)
332 if (!test[i].isPrint() && !test[i].isNull())
333 return false;
334 return true;
335}
336
338 return m_swordModule.getConfigEntry("UnlockInfo");
339}
340
344
346 return QStringLiteral("%1/%2").arg(getGlobalBaseIndexLocation(),
347 m_cachedName.toLocal8Bit());
348}
349
351 // This for now returns the location of the main index
352 return getModuleBaseIndexLocation() + QStringLiteral("/standard");
353}
354
356 { // Is this a directory?
357 QFileInfo fi(getModuleStandardIndexLocation());
358 if (!fi.isDir())
359 return false;
360 }
361
362 // Are the index version and module version OK?
363 QSettings module_config(getModuleBaseIndexLocation()
364 + QStringLiteral("/bibletime-index.conf"),
365 QSettings::IniFormat);
366
368 && module_config.value(QStringLiteral("module-version")).toString()
370 {
371 return false;
372 }
373
374 if (module_config.value(QStringLiteral("index-version")).toUInt()
375 != INDEX_VERSION)
376 {
377 qDebug("%s: INDEX_VERSION is not compatible with this version of "
378 "BibleTime.",
379 m_cachedName.toUtf8().constData());
380 return false;
381 }
382
383 // Is the index there?
384 return lucene::index::IndexReader::indexExists(getModuleStandardIndexLocation()
385 .toLatin1().constData());
386}
387
398
405
407 auto cleanup =
408 qScopeGuard(
409 [this]() noexcept
410 { m_cancelIndexing.store(false, std::memory_order_relaxed); });
411#define CANCEL_INDEXING (m_cancelIndexing.load(std::memory_order_relaxed))
412
413 try {
414 // Without this we don't get strongs, lemmas, etc.
415 m_backend.setFilterOptions(btConfig().getFilterOptions());
416 /* Make sure we reset all important filter options which influcence the
417 plain filters. Turn on these options, they are needed for the
418 EntryAttributes population */
420
421 /* We don't want the following in the text, the do not carry searchable
422 information. */
426
427 // Do not use any stop words:
428 Analyzer analyzer;
429 const QString index(getModuleStandardIndexLocation());
430
431 QDir dir(QStringLiteral("/"));
432 dir.mkpath(getGlobalBaseIndexLocation());
433 dir.mkpath(getModuleBaseIndexLocation());
434 dir.mkpath(getModuleStandardIndexLocation());
435
436 if (lucene::index::IndexReader::indexExists(index.toLatin1().constData()))
437 if (lucene::index::IndexReader::isLocked(index.toLatin1().constData()))
438 lucene::index::IndexReader::unlock(index.toLatin1().constData());
439
440 // Always create a new index:
441 auto writer =
442 std::make_optional<lucene::index::IndexWriter>(
443 index.toLatin1().constData(),
444 &analyzer,
445 true);
446 writer->setMaxFieldLength(BT_MAX_LUCENE_FIELD_LENGTH);
447 writer->setUseCompoundFile(true); // Merge segments into a single file
448
449 CSwordBibleModuleInfo *bm = qobject_cast<CSwordBibleModuleInfo*>(this);
450
451 unsigned long verseLowIndex;
452 unsigned long verseHighIndex;
453
454 if(bm)
455 {
456 verseLowIndex = bm->lowerBound().index();
457 verseHighIndex = bm->upperBound().index();
458 }
459 else
460 {
461 m_swordModule.setPosition(sword::TOP);
462 verseLowIndex = m_swordModule.getIndex();
463 m_swordModule.setPosition(sword::BOTTOM);
464 verseHighIndex = m_swordModule.getIndex();
465 }
466
467 // verseLowIndex is not 0 in all cases (i.e. NT-only modules)
468 unsigned long verseIndex = verseLowIndex + 1;
469 unsigned long verseSpan = verseHighIndex - verseLowIndex;
470
471 // Index() is not implemented properly for lexicons, so work around it:
473 verseIndex = 0;
474 verseLowIndex = 0;
475 verseSpan = static_cast<CSwordLexiconModuleInfo *>(this)->entries().size();
476 }
477
478 Q_EMIT indexingProgress(0);
479
480 sword::SWKey * const key = m_swordModule.getKey();
481 sword::VerseKey * const vk = dynamic_cast<sword::VerseKey *>(key);
482
483 if (vk) {
484 /* We have to be sure to insert the english key into the index,
485 otherwise we'd be in trouble if the language changes. */
486 vk->setLocale("en_US");
487 /* If we have a verse based module, we want to include the pre-
488 chapter etc. headings in the search. */
489 vk->setIntros(true);
490 }
491
492 QByteArray textBuffer; // Holds UTF-8 data and is faster than QString.
493
494 // we start with the first module entry, key is automatically updated
495 // because key is a pointer to the modules key
496 m_swordModule.setSkipConsecutiveLinks(true);
497
498 auto const sPwcharBuffer =
499 std::make_unique<wchar_t[]>(BT_MAX_LUCENE_FIELD_LENGTH + 1);
500 wchar_t * const wcharBuffer = sPwcharBuffer.get();
501 BT_ASSERT(wcharBuffer);
502
503 if(bm && vk) // Implied that vk could be null due to cast above
504 vk->setIndex(bm->lowerBound().index());
505 else
506 m_swordModule.setPosition(sword::TOP);
507
508 bool importantFilterOption = hasImportantFilterOption();
509
510 while (!(m_swordModule.popError()) && !CANCEL_INDEXING) {
511
512 /* Also index Chapter 0 and Verse 0, because they might have
513 information in the entry attributes. We used to just put their
514 content into the textBuffer and continue to the next verse, but
515 with entry attributes this doesn't work any more. Hits in the
516 search dialog will show up as 1:1 (instead of 0). */
517
518 lucene::document::Document doc;
519
520 #define U(C) \
521 static constexpr auto LUCENE_ ## C = \
522 std::to_underlying(lucene::document::Field::C)
523 U(STORE_YES); U(STORE_NO); U(INDEX_NO); U(INDEX_TOKENIZED);
524 #undef U
525
526 //index the key
527 lucene_utf8towcs(wcharBuffer, key->getText(), BT_MAX_LUCENE_FIELD_LENGTH);
528
529 doc.add(*(new lucene::document::Field(static_cast<const TCHAR *>(_T("key")),
530 static_cast<const TCHAR *>(wcharBuffer),
531 LUCENE_STORE_YES
532 | LUCENE_INDEX_NO)));
533
534 if (importantFilterOption) {
535 // Index text including strongs, morph, footnotes, and headings.
537 textBuffer.append(m_swordModule.stripText());
538 lucene_utf8towcs(wcharBuffer,
539 static_cast<const char *>(textBuffer),
541 doc.add(*(new lucene::document::Field(static_cast<const TCHAR *>(_T("content")),
542 static_cast<const TCHAR *>(wcharBuffer),
543 LUCENE_STORE_NO
544 | LUCENE_INDEX_TOKENIZED)));
545 textBuffer.clear();
546 }
547
548 // Index text without strongs, morph, footnotes, and headings.
550 textBuffer.append(m_swordModule.stripText());
551 lucene_utf8towcs(wcharBuffer,
552 static_cast<const char *>(textBuffer),
554 doc.add(*(new lucene::document::Field(static_cast<const TCHAR *>(_T("content")),
555 static_cast<const TCHAR *>(wcharBuffer),
556 LUCENE_STORE_NO
557 | LUCENE_INDEX_TOKENIZED)));
558 textBuffer.clear();
559
560 for (auto & vp : m_swordModule.getEntryAttributes()["Footnote"]) {
561 lucene_utf8towcs(wcharBuffer, vp.second["body"], BT_MAX_LUCENE_FIELD_LENGTH);
562 doc.add(*(new lucene::document::Field(static_cast<const TCHAR *>(_T("footnote")),
563 static_cast<const TCHAR *>(wcharBuffer),
564 LUCENE_STORE_NO
565 | LUCENE_INDEX_TOKENIZED)));
566 }
567
568 // Headings
569 for (auto & vp
570 : m_swordModule.getEntryAttributes()["Heading"]["Preverse"])
571 {
572 lucene_utf8towcs(wcharBuffer, vp.second, BT_MAX_LUCENE_FIELD_LENGTH);
573 doc.add(*(new lucene::document::Field(static_cast<const TCHAR *>(_T("heading")),
574 static_cast<const TCHAR *>(wcharBuffer),
575 LUCENE_STORE_NO
576 | LUCENE_INDEX_TOKENIZED)));
577 }
578
579 // Strongs/Morphs
580 for (auto const & vp : m_swordModule.getEntryAttributes()["Word"]) {
581 auto const & attrs = vp.second;
582 auto const partCountIter(attrs.find("PartCount"));
583 int partCount = (partCountIter != attrs.end())
584 ? QString(partCountIter->second).toInt()
585 : 0;
586 for (int i=0; i<partCount; i++) {
587
588 sword::SWBuf lemmaKey = "Lemma";
589 if (partCount > 1)
590 lemmaKey.appendFormatted(".%d", i+1);
591 auto const lemmaIter(attrs.find(lemmaKey));
592 if (lemmaIter != attrs.end()) {
593 lucene_utf8towcs(wcharBuffer, lemmaIter->second, BT_MAX_LUCENE_FIELD_LENGTH);
594 doc.add(*(new lucene::document::Field(static_cast<const TCHAR *>(_T("strong")),
595 static_cast<const TCHAR *>(wcharBuffer),
596 LUCENE_STORE_NO
597 | LUCENE_INDEX_TOKENIZED)));
598 }
599
600 }
601
602 auto const morphIter(attrs.find("Morph"));
603 if (morphIter != attrs.end()) {
604 lucene_utf8towcs(wcharBuffer, morphIter->second, BT_MAX_LUCENE_FIELD_LENGTH);
605 doc.add(*(new lucene::document::Field(static_cast<const TCHAR *>(_T("morph")),
606 static_cast<const TCHAR *>(wcharBuffer),
607 LUCENE_STORE_NO
608 | LUCENE_INDEX_TOKENIZED)));
609 }
610 }
611
612 writer->addDocument(&doc);
613 //Index() is not implemented properly for lexicons, so we use a
614 //workaround.
616 verseIndex++;
617 } else {
618 verseIndex = m_swordModule.getIndex();
619 }
620
621 if (verseIndex % 200 == 0) {
622 if (verseSpan == 0) { // Prevent division by zero
623 Q_EMIT indexingProgress(0);
624 } else {
625 Q_EMIT indexingProgress(
626 static_cast<int>(
627 (100 * (verseIndex - verseLowIndex))
628 / verseSpan));
629 }
630 }
631
632 m_swordModule.increment();
633 } // while (!(m_module.Error()) && !CANCEL_INDEXING)
634
635 if (!CANCEL_INDEXING)
636 writer->optimize();
637 writer->close();
638 writer.reset();
639
640 if (CANCEL_INDEXING) {
641 deleteIndex();
642 } else {
643 QSettings module_config(getModuleBaseIndexLocation()
644 + QStringLiteral("/bibletime-index.conf"),
645 QSettings::IniFormat);
647 module_config.setValue(QStringLiteral("module-version"),
649 module_config.setValue(QStringLiteral("index-version"),
651 Q_EMIT hasIndexChanged(true);
652 }
653 // } catch (CLuceneError & e) {
654 } catch (...) {
655 deleteIndex();
656 throw;
657 }
658}
659
664
665void CSwordModuleInfo::deleteIndexForModule(const QString & name) {
666 QDir(QStringLiteral("%1/%2").arg(getGlobalBaseIndexLocation(), name))
667 .removeRecursively();
668}
669
671 namespace DU = util::directory;
672 return DU::getDirSizeRecursive(getModuleBaseIndexLocation());
673}
674
676CSwordModuleInfo::searchIndexed(QString const & searchedText,
677 sword::ListKey const & scope) const
678{
679 auto const sPutfBuffer =
680 std::make_unique<char[]>(BT_MAX_LUCENE_FIELD_LENGTH + 1);
681 auto const sPwcharBuffer =
682 std::make_unique<wchar_t[]>(BT_MAX_LUCENE_FIELD_LENGTH + 1);
683 char * const utfBuffer = sPutfBuffer.get();
684 BT_ASSERT(utfBuffer);
685 wchar_t * const wcharBuffer = sPwcharBuffer.get();
686 BT_ASSERT(wcharBuffer);
687
688 // work around Swords thread insafety for Bibles and Commentaries
689 m_swordModule.setKey(createKey()->asSwordKey());
690
691 // do not use any stop words
692 Analyzer analyzer;
693 lucene::search::IndexSearcher searcher(getModuleStandardIndexLocation().toLatin1().constData());
694 lucene_utf8towcs(wcharBuffer, searchedText.toUtf8().constData(), BT_MAX_LUCENE_FIELD_LENGTH);
695 std::unique_ptr<lucene::search::Query> q(lucene::queryParser::QueryParser::parse(static_cast<const TCHAR *>(wcharBuffer),
696 static_cast<const TCHAR *>(_T("content")),
697 &analyzer));
698
699 std::unique_ptr<lucene::search::Hits> h(
700 searcher.search(q.get(), lucene::search::Sort::INDEXORDER()));
701
702 const bool useScope = (scope.getCount() > 0);
703
704 lucene::document::Document * doc = nullptr;
705 std::unique_ptr<sword::SWKey> swKey(m_swordModule.createKey());
706
707 sword::VerseKey * const vk = dynamic_cast<sword::VerseKey *>(swKey.get());
708 if (vk)
709 vk->setIntros(true);
710
712 for (size_t i = 0; i < h->length(); ++i) {
713 doc = &h->doc(i);
714 lucene_wcstoutf8(utfBuffer,
715 static_cast<const wchar_t *>(doc->get(static_cast<const TCHAR *>(_T("key")))),
717
718 swKey->setText(utfBuffer);
719
720 // Limit results based on scope:
721 if (useScope) {
722 for (int j = 0; j < scope.getCount(); j++) {
723 if (auto const * const vkey =
724 dynamic_cast<sword::VerseKey const *>(
725 scope.getElement(j)))
726 {
727 if (vkey->getLowerBound().compare(*swKey) <= 0
728 && vkey->getUpperBound().compare(*swKey) >= 0)
729 results.emplace_back(swKey->clone());
730 }
731 }
732 } else { // No scope, give me all buffers
733 results.emplace_back(swKey->clone());
734 }
735 }
736
737 return results;
738}
739
741 return sword::SWVersion(config(CSwordModuleInfo::MinimumSwordVersion)
742 .toUtf8().constData());
743}
744
746 switch (entry) {
747
748 case AboutInformation:
749 return getFormattedConfigEntry(QStringLiteral("About"));
750
751 case CipherKey: {
752 if (btConfig().getModuleEncryptionKey(m_cachedName).isNull()) {
753 return QString(m_swordModule.getConfigEntry("CipherKey")); // Fallback
754 } else {
756 }
757 }
758
759 case AbsoluteDataPath: {
760 auto path(getSimpleConfigEntry(QStringLiteral("AbsoluteDataPath")));
761 if (!path.endsWith('/')) /// \todo is this needed?
762 path.append('/');
763
764 return path;
765 }
766
767 case DataPath: {
768 QString path(getSimpleConfigEntry(QStringLiteral("DataPath")));
769
770 // Make sure we remove the dataFile part if it's a Lexicon
773 {
774 int pos = path.lastIndexOf('/'); // Last slash in the string
775 if (pos != -1)
776 path = path.left(pos + 1); // Include the slash
777 }
778 return path;
779 }
780
781 case Description:
782 return getFormattedConfigEntry(QStringLiteral("Description"));
783
784 case ModuleVersion: {
785 auto const version(getSimpleConfigEntry(QStringLiteral("Version")));
786 return version.isEmpty() ? QStringLiteral("1.0") : version;
787 }
788
789 case MinimumSwordVersion: {
790 auto const minimumVersion =
791 getSimpleConfigEntry(QStringLiteral("MinimumVersion"));
792 return minimumVersion.isEmpty()
793 ? QStringLiteral("0.0")
794 : minimumVersion;
795 }
796
797 case TextDir: {
798 auto const dir = getSimpleConfigEntry(QStringLiteral("Direction"));
799 return dir.isEmpty() ? QStringLiteral("LtoR") : dir;
800 }
801
802 case DisplayLevel: {
803 auto const level =
804 getSimpleConfigEntry(QStringLiteral("DisplayLevel"));
805 return level.isEmpty() ? QStringLiteral("1") : level;
806 }
807
808 case GlossaryFrom: {
810 return {};
811
812 return getSimpleConfigEntry(QStringLiteral("GlossaryFrom"));
813 }
814
815 case GlossaryTo: {
817 return {};
818
819 return getSimpleConfigEntry(QStringLiteral("GlossaryTo"));
820 }
821
822 case Markup: {
823 auto const markup =
824 getSimpleConfigEntry(QStringLiteral("SourceType"));
825 return markup.isEmpty() ? QStringLiteral("Unknown") : markup;
826 }
827
830 QStringLiteral("DistributionLicense"));
831
834 QStringLiteral("DistributionSource"));
835
837 return getFormattedConfigEntry(QStringLiteral("DistributionNotes"));
838
839 case TextSource:
840 return getFormattedConfigEntry(QStringLiteral("TextSource"));
841
842 case CopyrightNotes:
843 return getFormattedConfigEntry(QStringLiteral("CopyrightNotes"));
844
845 case CopyrightHolder:
846 return getFormattedConfigEntry(QStringLiteral("CopyrightHolder"));
847
848 case CopyrightDate:
849 return getFormattedConfigEntry(QStringLiteral("CopyrightDate"));
850
853 QStringLiteral("CopyrightContactName"));
854
857 QStringLiteral("CopyrightContactAddress"));
858
861 QStringLiteral("CopyrightContactEmail"));
862
863 default:
864 return {};
865 }
866}
867
869 /// \todo This is a BAD workaround to see if the filter is GBF, OSIS or ThML!
870 auto const & originalOptionName = option.configOptionName;
871 using namespace std::string_literals;
872 std::string const optionNames[] = {
873 "OSIS"s + originalOptionName,
874 "GBF"s + originalOptionName,
875 "ThML"s + originalOptionName,
876 "UTF8"s + originalOptionName,
877 originalOptionName
878 };
879 for (auto [it, end] =
880 m_swordModule.getConfig().equal_range("GlobalOptionFilter");
881 it != end;
882 ++it)
883 {
884 auto const & valueBuf = it->second;
885 std::string_view const value(valueBuf.c_str(), valueBuf.size());
886 for (auto const & optionName : optionNames)
887 if (value == optionName)
888 return true;
889 }
890 return false;
891}
892
894 return (config(TextDir) == QStringLiteral("RtoL"))
896 : LeftToRight;
897}
898
900{ return textDirection() == RightToLeft ? "rtl" : "ltr"; }
901
902void CSwordModuleInfo::write(CSwordKey * key, const QString & newText) {
903 m_swordModule.setKey(key->key().toUtf8().constData());
904
905 /* Don't store a pointer to the const char* value somewhere because QCString
906 doesn't keep the value of it. */
907 m_swordModule.setEntry(isUnicode()
908 ? newText.toUtf8().constData()
909 : newText.toLocal8Bit().constData());
910}
911
913 static auto const row(
914 QStringLiteral("<tr><td><b>%1</b></td><td>%2</td></tr>"));
915
916 auto text(QStringLiteral("<table>"));
917
918 text += row
919 .arg(tr("Version"))
921 ? config(CSwordModuleInfo::ModuleVersion).toHtmlEscaped()
922 : tr("unknown"));
923
924 {
925 const QString sourceType(m_swordModule.getConfigEntry("SourceType"));
926 text += row
927 .arg(tr("Markup"))
928 .arg(!sourceType.isEmpty()
929 ? sourceType.toHtmlEscaped()
930 : tr("unknown"));
931 }
932
933 text += row
934 .arg(tr("Location"))
935 .arg(config(CSwordModuleInfo::AbsoluteDataPath).toHtmlEscaped());
936
937 text += row
938 .arg(tr("Language"))
939 .arg(m_cachedLanguage->translatedName().toHtmlEscaped());
940
941 if (char const * const e = m_swordModule.getConfigEntry("Category"))
942 text += row.arg(tr("Category"))
943 .arg(QString{e}.toHtmlEscaped());
944
945 if (char const * const e = m_swordModule.getConfigEntry("LCSH"))
946 text += row.arg(tr("LCSH"))
947 .arg(QString{e}.toHtmlEscaped());
948
949 text += row
950 .arg(tr("Writable"))
951 .arg(isWritable() ? tr("yes") : tr("no"));
952
953 if (isEncrypted()) {
954 text += row
955 .arg(tr("Unlock key"))
956 .arg(config(CSwordModuleInfo::CipherKey).toHtmlEscaped());
957 if (char const * const e = m_swordModule.getConfigEntry("UnlockInfo"))
958 text += row.arg(tr("Unlock info")).arg(QString(e).toHtmlEscaped());
959 }
960
961 QString options;
962 static constexpr auto const allFilterOptions = {
963 &footnotes,
965 &headings,
966 &morphTags,
967 &lemmas,
975 };
976 for (auto const * const filterOption : allFilterOptions) {
977 if (has(*filterOption)) {
978 if (!options.isEmpty())
979 options += QStringLiteral(", ");
980 options += QObject::tr(filterOption->translatableOptionName);
981 }
982 }
983
984 if (!options.isEmpty())
985 text += row
986 .arg(tr("Features"))
987 .arg(options.toHtmlEscaped());
988
989 text += QStringLiteral("</table><hr>");
990
991 // Clearly say the module contains cult/questionable materials
993 text += QStringLiteral("<br/><b>%1</b><br/><br/>")
994 .arg(tr("Take care, this work contains cult / questionable "
995 "material!"));
996
997 text += QStringLiteral("<b>%1:</b><br/>%2")
998 .arg(tr("About"))
999 .arg(config(AboutInformation)); // May contain HTML, don't escape
1000
1001 text += QStringLiteral("<hr><table>");
1002
1003 struct Entry { ConfigEntry const type; char const * const text; };
1004 auto const entries = {
1005 Entry{DistributionLicense, QT_TR_NOOP("Distribution license")},
1006 Entry{DistributionSource, QT_TR_NOOP("Distribution source")},
1007 Entry{DistributionNotes, QT_TR_NOOP("Distribution notes")},
1008 Entry{TextSource, QT_TR_NOOP("Text source")},
1009 Entry{CopyrightNotes, QT_TR_NOOP("Copyright notes")},
1010 Entry{CopyrightHolder, QT_TR_NOOP("Copyright holder")},
1011 Entry{CopyrightDate, QT_TR_NOOP("Copyright date")},
1012 Entry{CopyrightContactName, QT_TR_NOOP("Copyright contact name")},
1013 Entry{CopyrightContactAddress, QT_TR_NOOP("Copyright contact address")},
1014 Entry{CopyrightContactEmail, QT_TR_NOOP("Copyright contact email")},
1015 };
1016 for (auto const & entry : entries) {
1017 auto const value = config(entry.type);
1018 if (!value.isEmpty())
1019 text += row.arg(tr(entry.text).toHtmlEscaped())
1020 .arg(value.toHtmlEscaped());
1021 }
1022
1023 return text + QStringLiteral("</table>");
1024}
1025
1026bool CSwordModuleInfo::isUnicode() const noexcept
1027{ return m_swordModule.isUnicode(); }
1028
1034
1035#define BT_CAT_ICON(Icon,icon) \
1036 QIcon const & \
1037 CSwordModuleInfo::category ## Icon( \
1038 CSwordModuleInfo::Category const category) \
1039 { \
1040 switch (category) { \
1041 case CSwordModuleInfo::Category::Bibles: \
1042 return CResMgr::categories::bibles::icon(); \
1043 case CSwordModuleInfo::Category::Commentaries: \
1044 return CResMgr::categories::commentaries::icon(); \
1045 case CSwordModuleInfo::Category::Books: \
1046 return CResMgr::categories::books::icon(); \
1047 case CSwordModuleInfo::Category::Lexicons: \
1048 return CResMgr::categories::lexicons::icon(); \
1049 case CSwordModuleInfo::Category::Glossaries: \
1050 return CResMgr::categories::glossaries::icon(); \
1051 case CSwordModuleInfo::Category::DailyDevotionals: \
1052 return CResMgr::categories::dailyDevotionals::icon(); \
1053 case CSwordModuleInfo::Category::MapsAndImages: \
1054 return CResMgr::categories::mapsAndImages::icon(); \
1055 default: \
1056 BT_ASSERT(category == \
1057 CSwordModuleInfo::Category::Questionable); \
1058 return CResMgr::categories::questionable::icon(); \
1059 } \
1060 }
1061BT_CAT_ICON(Icon, icon)
1062BT_CAT_ICON(IconAdd, icon_add)
1063BT_CAT_ICON(IconLocked, icon_locked)
1064#undef BT_CAT_ICON
1065
1066QIcon const &
1080
1082 switch (category) {
1084 return tr("Bibles");
1086 return tr("Commentaries");
1088 return tr("Books");
1090 return tr("Questionable");
1092 return tr("Maps and Images");
1094 return tr("Daily Devotionals");
1096 return tr("Lexicons and Dictionaries");
1098 return tr("Glossaries");
1099 default:
1100 return tr("Unknown");
1101 }
1102}
1103
1104QString CSwordModuleInfo::getSimpleConfigEntry(const QString & name) const {
1105 auto const * const value =
1106 m_swordModule.getConfigEntry(name.toUtf8().constData());
1107 return isUnicode() ? QString::fromUtf8(value) : QString::fromLatin1(value);
1108}
1109
1110/// \note See http://www.crosswire.org/wiki/DevTools:conf_Files#Localization
1111QString CSwordModuleInfo::getFormattedConfigEntry(const QString & name) const {
1112 auto const localeNames(
1113 QLocale(CSwordBackend::instance().booknameLanguage())
1114 .uiLanguages());
1115 for (int i = localeNames.size() - 1; i >= -1; --i) {
1116 sword::SWBuf RTF_Buffer;
1117 if (i < 0) {
1118 RTF_Buffer =
1119 m_swordModule.getConfigEntry(name.toUtf8().constData());
1120 } else {
1121 RTF_Buffer =
1122 m_swordModule.getConfigEntry(
1123 QStringLiteral("%1_%2")
1124 .arg(name, localeNames[i])
1125 .toUtf8().constData());
1126 }
1127 if (RTF_Buffer.length() > 0) {
1128 sword::RTFHTML RTF_Filter;
1129 RTF_Filter.processText(RTF_Buffer, nullptr, nullptr);
1130 return isUnicode()
1131 ? QString::fromUtf8(RTF_Buffer.c_str())
1132 : QString::fromLatin1(RTF_Buffer.c_str());
1133 }
1134 }
1135 return {};
1136}
1137
1139 if (m_hidden == hide)
1140 return false;
1141
1142 m_hidden = hide;
1143 static auto const configKey = QStringLiteral("state/hiddenModules");
1144 QStringList hiddenModules(btConfig().value<QStringList>(configKey));
1145 BT_ASSERT(hiddenModules.contains(m_cachedName) != hide);
1146 if (hide) {
1147 hiddenModules.append(m_cachedName);
1148 } else {
1149 hiddenModules.removeOne(m_cachedName);
1150 }
1151 btConfig().setValue(configKey, hiddenModules);
1152 Q_EMIT hiddenChanged(hide);
1153 return true;
1154}
#define BT_ASSERT(...)
Definition btassert.h:17
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition btconfig.h:305
T value(QString const &key, T const &defaultValue=T()) const
Returns the settings value for the given global key.
void setValue(QString const &key, T const &value)
Sets a value for a key.
QString getModuleEncryptionKey(const QString &name)
Function to get a module decryption key.
Definition btconfig.cpp:231
void setModuleEncryptionKey(const QString &name, const QString &key)
Function to set a module decryption key.
Definition btconfig.cpp:224
The backend layer main class, a backend implementation of Sword.
void setOption(CSwordModuleInfo::FilterOption const &type, const int state)
Sets the state of the given filter option.
sword::SWMgr & raw()
static CSwordBackend & instance() noexcept
void setFilterOptions(const FilterOptions &options)
sword::SWConfig * getConfig() const
Implementation for Sword Bibles.
CSwordVerseKey const & lowerBound() const
CSwordVerseKey const & upperBound() const
virtual QString key() const =0
bool has(CSwordModuleInfo::Feature const feature) const noexcept
CSwordModuleSearch::ModuleResultList searchIndexed(QString const &searchedText, sword::ListKey const &scope) const
sword::SWVersion minimumSwordVersion() const
void write(CSwordKey *key, const QString &newText)
QString config(const CSwordModuleInfo::ConfigEntry entry) const
bool isUnicode() const noexcept
static FilterOption const redLetterWords
static FilterOption const headings
virtual CSwordKey * createKey() const =0
static FilterOption const textualVariants
static void deleteIndexForModule(const QString &name)
static QIcon const & categoryIconAdd(CSwordModuleInfo::Category category)
bool unlock(const QString &unlockKey)
static FilterOption const hebrewPoints
static QString getGlobalBaseIndexLocation()
CSwordModuleInfo::Category const m_cachedCategory
ModuleType const m_type
static QString categoryName(const CSwordModuleInfo::Category &category)
static FilterOption const strongNumbers
bool unlockKeyIsValid() const
static FilterOption const morphTags
static QIcon const & categoryIconLocked(CSwordModuleInfo::Category category)
static FilterOption const scriptureReferences
void setImportantFilterOptions(bool enable)
QString getFormattedConfigEntry(const QString &name) const
QString const m_cachedName
QString getModuleBaseIndexLocation() const
static FilterOption const hebrewCantillation
CSwordModuleInfo::TextDirection textDirection() const
bool const m_cachedHasVersion
ModuleType type() const
QString const & name() const
void hasIndexChanged(bool hasIndex)
bool hasImportantFilterOption() const
QString aboutText() const
static FilterOption const morphSegmentation
QString getModuleStandardIndexLocation() const
char const * textDirectionAsHtml() const
sword::SWModule & m_swordModule
void hiddenChanged(bool hidden)
void indexingProgress(int)
bool setHidden(bool hide)
virtual bool isWritable() const
static FilterOption const greekAccents
QString getSimpleConfigEntry(const QString &name) const
std::atomic< bool > m_cancelIndexing
static QIcon const & categoryIcon(CSwordModuleInfo::Category category)
static FilterOption const footnotes
void unlockedChanged(bool unlocked)
std::shared_ptr< Language const > const m_cachedLanguage
CSwordBackend & m_backend
::qint64 indexSize() const
CSwordModuleInfo::Category category() const
CSwordModuleInfo(CSwordModuleInfo &&)=delete
static FilterOption const lemmas
long index() const
#define U(C)
#define BT_CAT_ICON(Icon, icon)
static constexpr unsigned long const BT_MAX_LUCENE_FIELD_LENGTH
#define T(f)
static constexpr unsigned const INDEX_VERSION
#define CANCEL_INDEXING
size_t lucene_utf8towcs(wchar_t *, const char *, size_t maxslen)
size_t lucene_wcstoutf8(char *, const wchar_t *, size_t maxslen)
std::vector< std::shared_ptr< sword::SWKey const > > ModuleResultList
CSwordModuleInfo::Category retrieveCategory(CSwordModuleInfo::ModuleType const type, CSwordModuleInfo::Features const features, sword::SWModule &module)
CSwordModuleInfo::Features retrieveFeatures(sword::SWModule const &module)
const QDir & getUserIndexDir()
static char const * valueToReadings(int value) noexcept
static char const * valueToOnOff(int value) noexcept