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