PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/tags/rel-1-7-alpha1/bibletime/src/backend/drivers/cswordmoduleinfo.cpp

#
C++ | 953 lines | 638 code | 209 blank | 106 comment | 138 complexity | ae7cd89d72c4a5cf5a53fd5fd7c2ad01 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0
  1. /*********
  2. *
  3. * This file is part of BibleTime's source code, http://www.bibletime.info/.
  4. *
  5. * Copyright 1999-2008 by the BibleTime developers.
  6. * The BibleTime source code is licensed under the GNU General Public License version 2.0.
  7. *
  8. **********/
  9. //BibleTime includes
  10. #include "cswordmoduleinfo.h"
  11. #include "cswordmoduleinfo.moc"
  12. #include "cswordlexiconmoduleinfo.h"
  13. #include "backend/managers/cswordbackend.h"
  14. #include "backend/cswordmodulesearch.h"
  15. #include "backend/keys/cswordkey.h"
  16. #include "backend/rendering/centrydisplay.h"
  17. #include "backend/managers/clanguagemgr.h"
  18. #include "util/directoryutil.h"
  19. #include "util/cpointers.h"
  20. #include "frontend/cbtconfig.h"
  21. #include <sys/types.h>
  22. #include <unistd.h>
  23. #include <stddef.h>
  24. #include <dirent.h>
  25. #include <regex.h>
  26. #include <boost/scoped_ptr.hpp>
  27. //Qt includes
  28. #include <QRegExp>
  29. #include <QDir>
  30. #include <QFileInfo>
  31. #include <QList>
  32. #include <QByteArray>
  33. #include <QDebug>
  34. #include <QSettings>
  35. //Sword includes
  36. #include <swbuf.h>
  37. #include <swkey.h>
  38. #include <listkey.h>
  39. #include <versekey.h>
  40. #include <swconfig.h>
  41. #include <rtfhtml.h>
  42. //Lucence includes
  43. #include <CLucene.h>
  44. #include <CLucene/util/Reader.h>
  45. #include <CLucene/util/Misc.h>
  46. #include <CLucene/util/dirent.h>
  47. //KDE includes
  48. //Increment this, if the index format changes
  49. //Then indices on the user's systems will be rebuilt
  50. const unsigned int INDEX_VERSION = 6;
  51. //Maximum index entry size, 1MiB for now
  52. //Lucene default is too small
  53. const unsigned long BT_MAX_LUCENE_FIELD_LENGTH = 1024*1024;
  54. CSwordModuleInfo::CSwordModuleInfo(sword::SWModule * module, CSwordBackend * const usedBackend) {
  55. m_module = module;
  56. Q_ASSERT(module);
  57. m_hidden = false;
  58. m_searchResult.ClearList();
  59. m_backend = usedBackend ? usedBackend : CPointers::backend();
  60. m_dataCache.name = module ? QString(module->Name()) : QString::null;
  61. m_dataCache.isUnicode = module ? module->isUnicode() : false;
  62. m_dataCache.category = UnknownCategory;
  63. m_dataCache.language = 0;
  64. m_dataCache.hasVersion = !QString((*m_backend->getConfig())[module->Name()]["Version"]).isEmpty();
  65. if (backend()) {
  66. if (hasVersion() && (minimumSwordVersion() > sword::SWVersion::currentVersion)) {
  67. qWarning("The module \"%s\" requires a newer Sword library. Please update to \"Sword %s\".",
  68. name().toUtf8().constData(), (const char *)minimumSwordVersion());
  69. }
  70. }
  71. }
  72. CSwordModuleInfo::CSwordModuleInfo(const CSwordModuleInfo & m) {
  73. m_module = m.m_module;
  74. m_backend = m.m_backend;
  75. m_dataCache = m.m_dataCache;
  76. m_searchResult = m.m_searchResult;
  77. m_hidden = m.m_hidden;
  78. }
  79. /** No descriptions */
  80. CSwordModuleInfo *CSwordModuleInfo::clone() {
  81. return new CSwordModuleInfo(*this);
  82. }
  83. CSwordModuleInfo::~CSwordModuleInfo() {
  84. m_searchResult.ClearList();
  85. m_module = 0; //the Sword module object is deleted by the backend
  86. }
  87. /** Sets the unlock key of the modules and writes the key into the cofig file.*/
  88. const bool CSwordModuleInfo::unlock(const QString & unlockKey) {
  89. if (!isEncrypted()) {
  90. return false;
  91. }
  92. CBTConfig::setModuleEncryptionKey(name(), unlockKey);
  93. backend()->setCipherKey(m_module->Name(), unlockKey.toUtf8().constData());
  94. //TODO: write to Sword config as well
  95. return true;
  96. }
  97. /** This function returns true if this module is locked, otherwise return false. */
  98. const bool CSwordModuleInfo::isLocked() {
  99. //still works, but the cipherkey is stored in CBTConfig.
  100. //Works because it is set in sword on program startup.
  101. if (isEncrypted()) {
  102. if (unlockKeyIsValid()) {
  103. return false;
  104. }
  105. return true;
  106. }
  107. return false;
  108. }
  109. /** This functions returns true if this module is encrypted (locked or unlocked). */
  110. const bool CSwordModuleInfo::isEncrypted() const {
  111. /**
  112. * If we have the CipherKey entry the module
  113. * is encrypted but not necessarily locked
  114. */
  115. //This code is still right, though we do no longer write to the module config files any more
  116. sword::ConfigEntMap config = backend()->getConfig()->Sections.find(name().toUtf8().constData())->second;
  117. sword::ConfigEntMap::iterator it = config.find("CipherKey");
  118. if (it != config.end()) {
  119. return true;
  120. }
  121. return false;
  122. }
  123. /** This function makes an estimate if a module was properly unlocked.
  124. * It returns true if the first entry of the module is not empty and
  125. * contains only printable characters (for the first 100 chars or so).
  126. * If that is the case, we can safely assume that a) the module was properly
  127. * unlocked and b) no buffer overflows will occur, which can happen when
  128. * Sword filters process garbage text which was not properly decrypted.
  129. */
  130. const bool CSwordModuleInfo::unlockKeyIsValid() {
  131. (*m_module) = sword::TOP;
  132. // This needs to use ::fromLatin1 because if the text is still locked,
  133. // a lot of garbage will show up. It will also work with properly decrypted
  134. // Unicode text, because all non-ASCII Unicode chars consist of bytes >127
  135. // and therefore contain no control (nonprintable) characters, which are all <127.
  136. QString test = isUnicode()
  137. ? QString::fromUtf8(m_module->getRawEntryBuf().c_str())
  138. : QString::fromLatin1( m_module->getRawEntryBuf().c_str() );
  139. if (test.isEmpty()) {
  140. qWarning() << "Unlock key of module" << name() << "is NOT valid!";
  141. return false;
  142. }
  143. for (unsigned int i = 0; i <= test.length() && i < 100; i++) {
  144. if ( !test[i].isPrint() && !test[i].isNull() ) {
  145. qWarning() << "Unlock key of module" << name() << "is NOT valid!";
  146. return false;
  147. }
  148. }
  149. qDebug() << "Unlock key of module" << name() << "is valid";
  150. return true;
  151. }
  152. const QString CSwordModuleInfo::getGlobalBaseIndexLocation() {
  153. return util::filesystem::DirectoryUtil::getUserIndexDir().absolutePath();
  154. }
  155. const QString CSwordModuleInfo::getModuleBaseIndexLocation() const {
  156. return getGlobalBaseIndexLocation() + QString("/") + name().toLocal8Bit();
  157. }
  158. const QString CSwordModuleInfo::getModuleStandardIndexLocation() const { //this for now returns the location of the main index
  159. return getModuleBaseIndexLocation() + QString("/standard");
  160. }
  161. const bool CSwordModuleInfo::hasIndex() {
  162. //this will return true only
  163. //if the index exists and has correct version information for both index and module
  164. QDir d;
  165. if (!d.exists( getModuleStandardIndexLocation() )) {
  166. return false;
  167. }
  168. //first check if the index version and module version are ok
  169. QSettings module_config(getModuleBaseIndexLocation() + QString("/bibletime-index.conf"), QSettings::IniFormat);
  170. if (hasVersion()) {
  171. if (module_config.value("module-version") != QString(config(CSwordModuleInfo::ModuleVersion)) ) {
  172. return false;
  173. }
  174. }
  175. if (module_config.value("index-version") != QString::number( INDEX_VERSION )) {
  176. qDebug("%s: INDEX_VERSION is not compatible with this version of BibleTime.", name().toUtf8().constData());
  177. return false;
  178. }
  179. //then check if the index is there
  180. return lucene::index::IndexReader::indexExists(getModuleStandardIndexLocation().toAscii().constData());
  181. }
  182. void CSwordModuleInfo::buildIndex() {
  183. wchar_t wcharBuffer[BT_MAX_LUCENE_FIELD_LENGTH + 1];
  184. //we don't want the linked entries indexed again
  185. module()->setSkipConsecutiveLinks(true);
  186. //Without this we don't get strongs, lemmas, etc
  187. backend()->setFilterOptions ( CBTConfig::getFilterOptionDefaults() );
  188. //make sure we reset all important filter options which influcence the plain filters.
  189. backend()->setOption( CSwordModuleInfo::strongNumbers, false );
  190. backend()->setOption( CSwordModuleInfo::morphTags, false );
  191. backend()->setOption( CSwordModuleInfo::morphSegmentation, false );
  192. backend()->setOption( CSwordModuleInfo::footnotes, false );
  193. backend()->setOption( CSwordModuleInfo::headings, false );
  194. backend()->setOption( CSwordModuleInfo::scriptureReferences, false );
  195. backend()->setOption( CSwordModuleInfo::redLetterWords, false );
  196. // do not use any stop words
  197. const TCHAR* stop_words[] = { NULL };
  198. lucene::analysis::standard::StandardAnalyzer an( stop_words );
  199. QString index = getModuleStandardIndexLocation();
  200. QDir dir("/");
  201. dir.mkpath( getGlobalBaseIndexLocation() );
  202. dir.mkpath( getModuleBaseIndexLocation() );
  203. dir.mkpath( getModuleStandardIndexLocation() );
  204. if (lucene::index::IndexReader::indexExists(index.toAscii().constData())) {
  205. if (lucene::index::IndexReader::isLocked(index.toAscii().constData()) ) {
  206. lucene::index::IndexReader::unlock(index.toAscii().constData());
  207. }
  208. }
  209. boost::scoped_ptr<lucene::index::IndexWriter> writer( new lucene::index::IndexWriter(index.toAscii().constData(), &an, true) ); //always create a new index
  210. writer->setMaxFieldLength(BT_MAX_LUCENE_FIELD_LENGTH);
  211. writer->setUseCompoundFile(true); //merge segments into a single file
  212. writer->setMinMergeDocs(1000);
  213. *m_module = sword::TOP;
  214. unsigned long verseLowIndex = m_module->Index();
  215. *m_module = sword::BOTTOM;
  216. unsigned long verseHighIndex = m_module->Index();
  217. //verseLowIndex is not 0 in all cases (i.e. NT-only modules)
  218. unsigned long verseIndex = verseLowIndex + 1;
  219. unsigned long verseSpan = verseHighIndex - verseLowIndex;
  220. //Index() is not implemented properly for lexicons, so we use a
  221. //workaround.
  222. if (type() == CSwordModuleInfo::Lexicon){
  223. verseIndex = 0;
  224. verseLowIndex = 0;
  225. verseSpan = ((CSwordLexiconModuleInfo*)this)->entries()->size();
  226. }
  227. const bool isVerseModule = (type() == CSwordModuleInfo::Bible) || (type() == CSwordModuleInfo::Commentary);
  228. emit indexingProgress(0);
  229. sword::SWKey* key = m_module->getKey();
  230. //VerseKey for bibles
  231. sword::VerseKey* vk = dynamic_cast<sword::VerseKey*>(key);
  232. if (vk) {
  233. // we have to be sure to insert the english key into the index, otherwise we'd be in trouble if the language changes
  234. vk->setLocale("en_US");
  235. //If we have a verse based module, we want to include the pre-chapter etc. headings in the search
  236. vk->Headings(1);
  237. }
  238. //holds UTF-8 data and is faster than QString.
  239. QByteArray textBuffer;
  240. // we start with the first module entry, key is automatically updated
  241. // because key is a pointer to the modules key
  242. m_module->setSkipConsecutiveLinks(true);
  243. for (*m_module = sword::TOP; !(m_module->Error()); (*m_module)++) {
  244. //If it is a sword-heading, store in buffer and index later in Verse X:1
  245. if (vk) {
  246. if (vk->Verse() == 0) {
  247. textBuffer.append( m_module->StripText() );
  248. continue;
  249. }
  250. }
  251. boost::scoped_ptr<lucene::document::Document> doc(new lucene::document::Document());
  252. //index the key
  253. lucene_utf8towcs(wcharBuffer, key->getText(), BT_MAX_LUCENE_FIELD_LENGTH);
  254. doc->add(*lucene::document::Field::UnIndexed(_T("key"), wcharBuffer));
  255. // index the main text
  256. //at this point we have to make sure we disabled the strongs and the other options
  257. //so the plain filters won't include the numbers somehow.
  258. lucene_utf8towcs(wcharBuffer, (const char*) textBuffer.append(m_module->StripText()), BT_MAX_LUCENE_FIELD_LENGTH);
  259. doc->add(*lucene::document::Field::UnStored(_T("content"), wcharBuffer));
  260. textBuffer.resize(0); //clean up
  261. // index attributes
  262. sword::AttributeList::iterator attListI;
  263. sword::AttributeValue::iterator attValueI;
  264. // Footnotes
  265. for (attListI = m_module->getEntryAttributes()["Footnote"].begin();
  266. attListI != m_module->getEntryAttributes()["Footnote"].end();
  267. attListI++) {
  268. lucene_utf8towcs(wcharBuffer, attListI->second["body"], BT_MAX_LUCENE_FIELD_LENGTH);
  269. doc->add(*lucene::document::Field::UnStored(_T("footnote"), wcharBuffer));
  270. } // for attListI
  271. // Headings
  272. for (attValueI = m_module->getEntryAttributes()["Heading"]["Preverse"].begin();
  273. attValueI != m_module->getEntryAttributes()["Heading"]["Preverse"].end();
  274. attValueI++) {
  275. lucene_utf8towcs(wcharBuffer, attValueI->second, BT_MAX_LUCENE_FIELD_LENGTH);
  276. doc->add(*lucene::document::Field::UnStored(_T("heading"), wcharBuffer));
  277. } // for attValueI
  278. // Strongs/Morphs
  279. for (attListI = m_module->getEntryAttributes()["Word"].begin();
  280. attListI != m_module->getEntryAttributes()["Word"].end();
  281. attListI++) {
  282. // for each attribute
  283. if (attListI->second["LemmaClass"] == "strong") {
  284. lucene_utf8towcs(wcharBuffer, attListI->second["Lemma"], BT_MAX_LUCENE_FIELD_LENGTH);
  285. doc->add(*lucene::document::Field::UnStored(_T("strong"), wcharBuffer));
  286. //qWarning("Adding strong %s", attListI->second["Lemma"].c_str());
  287. }
  288. if (attListI->second.find("Morph") != attListI->second.end()) {
  289. lucene_utf8towcs(wcharBuffer, attListI->second["Morph"], BT_MAX_LUCENE_FIELD_LENGTH);
  290. doc->add(*lucene::document::Field::UnStored(_T("morph"), wcharBuffer));
  291. }
  292. } // for attListI
  293. writer->addDocument(doc.get());
  294. //Index() is not implemented properly for lexicons, so we use a
  295. //workaround.
  296. if (type() == CSwordModuleInfo::Lexicon){
  297. verseIndex++;
  298. }
  299. else{
  300. verseIndex = m_module->Index();
  301. }
  302. if (verseIndex % 200 == 0) {
  303. int indexingProgressValue;
  304. if (verseSpan == 0) { //prevent division by zero
  305. //m_indexingProgress.setValue( QVariant(0) );
  306. indexingProgressValue = 0;
  307. } else {
  308. //m_indexingProgress.setValue( QVariant((int)((100*(verseIndex-verseLowIndex))/(verseHighIndex-verseLowIndex))) );
  309. indexingProgressValue = (int)((100*(verseIndex-verseLowIndex)) / (verseSpan));
  310. }
  311. //m_indexingProgress.activate();
  312. emit indexingProgress(indexingProgressValue);
  313. }
  314. }
  315. writer->optimize();
  316. writer->close();
  317. QSettings module_config(getModuleBaseIndexLocation() + QString("/bibletime-index.conf"), QSettings::IniFormat);
  318. if (hasVersion()) module_config.setValue("module-version", config(CSwordModuleInfo::ModuleVersion) );
  319. module_config.setValue("index-version", INDEX_VERSION );
  320. }
  321. void CSwordModuleInfo::deleteIndexForModule( QString name ) {
  322. util::filesystem::DirectoryUtil::removeRecursive( getGlobalBaseIndexLocation() + "/" + name );
  323. }
  324. unsigned long CSwordModuleInfo::indexSize() const {
  325. return util::filesystem::DirectoryUtil::getDirSizeRecursive( getModuleBaseIndexLocation() );
  326. }
  327. const bool CSwordModuleInfo::searchIndexed(const QString& searchedText, sword::ListKey& scope) {
  328. char utfBuffer[BT_MAX_LUCENE_FIELD_LENGTH + 1];
  329. wchar_t wcharBuffer[BT_MAX_LUCENE_FIELD_LENGTH + 1];
  330. // work around Swords thread insafety for Bibles and Commentaries
  331. boost::scoped_ptr < CSwordKey > key(CSwordKey::createInstance(this));
  332. sword::SWKey* s = dynamic_cast < sword::SWKey * >(key.get());
  333. QList<sword::VerseKey*> list;
  334. const bool isVerseModule = (type() == CSwordModuleInfo::Bible) || (type() == CSwordModuleInfo::Commentary);
  335. if (s) {
  336. m_module->SetKey(*s);
  337. }
  338. m_searchResult.ClearList();
  339. try {
  340. // do not use any stop words
  341. const TCHAR* stop_words[] = { NULL };
  342. lucene::analysis::standard::StandardAnalyzer analyzer( stop_words );
  343. lucene::search::IndexSearcher searcher(getModuleStandardIndexLocation().toAscii().constData());
  344. lucene_utf8towcs(wcharBuffer, searchedText.toUtf8().constData(), BT_MAX_LUCENE_FIELD_LENGTH);
  345. boost::scoped_ptr<lucene::search::Query> q( lucene::queryParser::QueryParser::parse(wcharBuffer, _T("content"), &analyzer) );
  346. boost::scoped_ptr<lucene::search::Hits> h( searcher.search(q.get(), lucene::search::Sort::INDEXORDER) );
  347. const bool useScope = (scope.Count() > 0);
  348. // const bool isVerseModule = (type() == CSwordModuleInfo::Bible) || (type() == CSwordModuleInfo::Commentary);
  349. lucene::document::Document* doc = 0;
  350. boost::scoped_ptr<sword::SWKey> swKey( module()->CreateKey() );
  351. for (int i = 0; i < h->length(); ++i) {
  352. doc = &h->doc(i);
  353. lucene_wcstoutf8(utfBuffer, doc->get(_T("key")), BT_MAX_LUCENE_FIELD_LENGTH);
  354. swKey->setText(utfBuffer);
  355. // limit results based on scope
  356. //if (searchOptions & CSwordModuleSearch::useScope && scope.Count() > 0){
  357. if (useScope) {
  358. for (int j = 0; j < scope.Count(); j++) {
  359. sword::VerseKey* vkey = dynamic_cast<sword::VerseKey*>(scope.getElement(j));
  360. if (vkey->LowerBound().compare(*swKey) <= 0 && vkey->UpperBound().compare(*swKey) >= 0) {
  361. m_searchResult.add(*swKey);
  362. }
  363. }
  364. } else { // no scope, give me all buffers
  365. m_searchResult.add(*swKey);
  366. }
  367. }
  368. }
  369. catch (...) {
  370. qWarning("CLucene exception");
  371. return false;
  372. }
  373. qDeleteAll(list);
  374. list.clear();
  375. return (m_searchResult.Count() > 0);
  376. }
  377. void CSwordModuleInfo::connectIndexingFinished(QObject* receiver, const char* slot) {
  378. //m_indexingFinished.connect(receiver, slot);
  379. QObject::connect(this, SIGNAL(indexingFinished()), receiver, slot);
  380. }
  381. void CSwordModuleInfo::connectIndexingProgress(QObject* receiver, const char* slot) {
  382. //m_indexingProgress.connect(receiver, slot);
  383. QObject::connect(this, SIGNAL(indexingProgress(int)), receiver, slot);
  384. }
  385. void CSwordModuleInfo::disconnectIndexingSignals(QObject* receiver) {
  386. //m_indexingProgress.disconnect(receiver);
  387. //m_indexingFinished.disconnect(receiver);
  388. QObject::disconnect(this, SIGNAL(indexingProgress(int)), receiver, 0);
  389. QObject::disconnect(this, SIGNAL(indexingFinished()), receiver, 0);
  390. }
  391. /** Returns the last search result for this module. */
  392. sword::ListKey & CSwordModuleInfo::searchResult(const sword::ListKey * newResult) {
  393. if (newResult) {
  394. m_searchResult.copyFrom(*newResult);
  395. }
  396. return m_searchResult;
  397. }
  398. /** Clears the last search result. */
  399. void CSwordModuleInfo::clearSearchResult() {
  400. m_searchResult.ClearList();
  401. }
  402. /** Returns the required Sword version for this module. Returns -1 if no special Sword version is required. */
  403. const sword::SWVersion CSwordModuleInfo::minimumSwordVersion() {
  404. return sword::SWVersion(config(CSwordModuleInfo::MinimumSwordVersion).toUtf8().constData());
  405. }
  406. const QString CSwordModuleInfo::config(const CSwordModuleInfo::ConfigEntry entry) const {
  407. switch (entry) {
  408. case AboutInformation:
  409. return getFormattedConfigEntry("About");
  410. case CipherKey: {
  411. if (CBTConfig::getModuleEncryptionKey(name()).isNull()) { //fall back!
  412. return QString(m_module->getConfigEntry("CipherKey"));
  413. }
  414. else {
  415. return CBTConfig::getModuleEncryptionKey(name());
  416. }
  417. }
  418. case AbsoluteDataPath: {
  419. QString path( getSimpleConfigEntry("AbsoluteDataPath") );
  420. path.replace(QRegExp("/./"), "/"); // make /abs/path/./modules/ looking better
  421. //make sure we have a trailing slash!
  422. if (path.right(1) != "/") {
  423. path.append('/');
  424. }
  425. return path;
  426. }
  427. case DataPath: { //make sure we remove the dataFile part if it's a Lexicon
  428. QString path(getSimpleConfigEntry("DataPath"));
  429. if ((type() == CSwordModuleInfo::GenericBook) || (type() == CSwordModuleInfo::Lexicon)) {
  430. int pos = path.lastIndexOf("/"); //last slash in the string
  431. if (pos != -1) {
  432. path = path.left(pos + 1); //include the slash
  433. }
  434. }
  435. return path;
  436. }
  437. case Description:
  438. return getFormattedConfigEntry("Description");
  439. case ModuleVersion: {
  440. QString version(getSimpleConfigEntry("Version"));
  441. if (version.isEmpty()) {
  442. version = "1.0";
  443. }
  444. return version;
  445. }
  446. case MinimumSwordVersion: {
  447. const QString minimumVersion(getSimpleConfigEntry("MinimumVersion"));
  448. return !minimumVersion.isEmpty()? minimumVersion : QString("0.0");
  449. }
  450. case TextDir: {
  451. const QString dir(getSimpleConfigEntry("Direction"));
  452. return !dir.isEmpty()? dir : QString("LtoR");
  453. }
  454. case DisplayLevel: {
  455. const QString level(getSimpleConfigEntry("DisplayLevel"));
  456. return !level.isEmpty()? level : QString("1");
  457. }
  458. case GlossaryFrom: {
  459. if (!category() == Glossary) {
  460. return QString::null;
  461. };
  462. const QString lang(getSimpleConfigEntry("GlossaryFrom"));
  463. return !lang.isEmpty()? lang : QString::null;
  464. }
  465. case GlossaryTo: {
  466. if (!category() == Glossary) {
  467. return QString::null;
  468. };
  469. const QString lang(getSimpleConfigEntry("GlossaryTo"));
  470. return !lang.isEmpty()? lang : QString::null;
  471. }
  472. case Markup: {
  473. const QString markup(getSimpleConfigEntry("SourceType"));
  474. return !markup.isEmpty()? markup : QString("Unknown");
  475. }
  476. case DistributionLicense:
  477. return getSimpleConfigEntry("DistributionLicense");
  478. case DistributionSource:
  479. return getSimpleConfigEntry("DistributionSource");
  480. case DistributionNotes:
  481. return getSimpleConfigEntry("DistributionNotes");
  482. case TextSource:
  483. return getSimpleConfigEntry("TextSource");
  484. case CopyrightNotes:
  485. return getSimpleConfigEntry("CopyrightNotes");
  486. case CopyrightHolder:
  487. return getSimpleConfigEntry("CopyrightHolder");
  488. case CopyrightDate:
  489. return getSimpleConfigEntry("CopyrightDate");
  490. case CopyrightContactName:
  491. return getSimpleConfigEntry("CopyrightContactName");
  492. case CopyrightContactAddress:
  493. return getSimpleConfigEntry("CopyrightContactAddress");
  494. case CopyrightContactEmail:
  495. return getSimpleConfigEntry("CopyrightContactEmail");
  496. default:
  497. return QString::null;
  498. }
  499. }
  500. /** Returns true if the module supports the feature given as parameter. */
  501. const bool CSwordModuleInfo::has(const CSwordModuleInfo::Feature feature) const {
  502. switch (feature) {
  503. // case StrongsNumbers:
  504. // return m_module->getConfig().has("Feature", "StrongsNumber");
  505. case GreekDef:
  506. return m_module->getConfig().has("Feature", "GreekDef");
  507. case HebrewDef:
  508. return m_module->getConfig().has("Feature", "HebrewDef");
  509. case GreekParse:
  510. return m_module->getConfig().has("Feature", "GreekParse");
  511. case HebrewParse:
  512. return m_module->getConfig().has("Feature", "HebrewParse");
  513. }
  514. return false;
  515. }
  516. const bool CSwordModuleInfo::has(const CSwordModuleInfo::FilterTypes option) const {
  517. //BAD workaround to see if the filter is GBF, OSIS or ThML!
  518. const QString name = backend()->configOptionName(option);
  519. if (m_module->getConfig().has("GlobalOptionFilter", QString("OSIS").append(name).toUtf8().constData())) {
  520. return true;
  521. }
  522. if (m_module->getConfig().has("GlobalOptionFilter", QString("GBF").append(name).toUtf8().constData())) {
  523. return true;
  524. }
  525. if (m_module->getConfig().has("GlobalOptionFilter", QString("ThML").append(name).toUtf8().constData())) {
  526. return true;
  527. }
  528. if (m_module->getConfig().has("GlobalOptionFilter", QString("UTF8").append(name).toUtf8().constData())) {
  529. return true;
  530. }
  531. if (m_module->getConfig().has("GlobalOptionFilter", name.toUtf8().constData())) {
  532. return true;
  533. }
  534. return false;
  535. }
  536. /** Returns the text direction of the module's text., */
  537. const CSwordModuleInfo::TextDirection CSwordModuleInfo::textDirection() {
  538. if (config(TextDir) == "RtoL") {
  539. return CSwordModuleInfo::RightToLeft;
  540. }
  541. else {
  542. return CSwordModuleInfo::LeftToRight;
  543. }
  544. }
  545. /** Writes the new text at the given position into the module. This does only work for writable modules. */
  546. void CSwordModuleInfo::write(CSwordKey * key, const QString & newText) {
  547. module()->KeyText(key->key().toUtf8().constData());
  548. //don't store a pointer to the const char* value somewhere because QCString doesn't keep the value of it
  549. module()->setEntry(isUnicode()? newText.toUtf8().constData() : newText.toLocal8Bit().constData());
  550. }
  551. /** Deletes the current entry and removes it from the module. */
  552. const bool CSwordModuleInfo::deleteEntry(CSwordKey * const key) {
  553. module()->KeyText(isUnicode()? key->key().toUtf8().constData() : key->key().toLocal8Bit().constData());
  554. if (module()) {
  555. module()->deleteEntry();
  556. return true;
  557. }
  558. return false;
  559. }
  560. /** Returns the category of this module. See CSwordModuleInfo::Category for possible values. */
  561. const CSwordModuleInfo::Category CSwordModuleInfo::category() const {
  562. //qDebug("CSwordModuleInfo::category");
  563. if (m_dataCache.category == CSwordModuleInfo::UnknownCategory) {
  564. const QString cat(m_module->getConfigEntry("Category"));
  565. //qDebug() << "the category was unknown, add a category "<< cat << "for module" << m_module->Name();
  566. if (cat == "Cults / Unorthodox / Questionable Material") {
  567. m_dataCache.category = Cult;
  568. }
  569. else if (cat == "Daily Devotional" || m_module->getConfig().has("Feature", "DailyDevotion")) {
  570. m_dataCache.category = DailyDevotional;
  571. }
  572. else if (cat == "Glossaries" || m_module->getConfig().has("Feature", "Glossary")) { //allow both
  573. m_dataCache.category = Glossary;
  574. }
  575. else if (cat == "Images" || cat == "Maps") {
  576. m_dataCache.category = Images;
  577. }
  578. else if (type() == Commentary) {
  579. m_dataCache.category = Commentaries;
  580. }
  581. else if (type() == Bible) {
  582. m_dataCache.category = Bibles;
  583. }
  584. else if (type() == Lexicon) {
  585. m_dataCache.category = Lexicons;
  586. }
  587. else if (type() == GenericBook) {
  588. m_dataCache.category = Books;
  589. }
  590. }
  591. //qDebug() << "assigned category: " << m_dataCache.category;
  592. return m_dataCache.category;
  593. }
  594. /** Returns the display object for this module. */
  595. Rendering::CEntryDisplay * const CSwordModuleInfo::getDisplay() const {
  596. return dynamic_cast < Rendering::CEntryDisplay * >(m_module->Disp());
  597. }
  598. QString CSwordModuleInfo::aboutText() const {
  599. QString text;
  600. text += "<table>";
  601. text += QString("<tr><td><b>%1</b></td><td>%2</td><tr>")
  602. .arg(tr("Version"))
  603. .arg(hasVersion()? config(CSwordModuleInfo::ModuleVersion) : tr("unknown"));
  604. text += QString("<tr><td><b>%1</b></td><td>%2</td></tr>")
  605. .arg(tr("Markup"))
  606. .arg(!QString(m_module->getConfigEntry("SourceType")).isEmpty()? QString(m_module->
  607. getConfigEntry("SourceType")) : tr("unknown"));
  608. text += QString("<tr><td><b>%1</b></td><td>%2</td></tr>")
  609. .arg(tr("Location"))
  610. .arg(config(CSwordModuleInfo::AbsoluteDataPath));
  611. text += QString("<tr><td><b>%1</b></td><td>%2</td></tr>")
  612. .arg(tr("Language"))
  613. .arg(language()->translatedName());
  614. if (m_module->getConfigEntry("Category"))
  615. text += QString("<tr><td><b>%1</b></td><td>%2</td></tr>")
  616. .arg(tr("Category"))
  617. .arg(m_module->getConfigEntry("Category"));
  618. if (m_module->getConfigEntry("LCSH"))
  619. text += QString("<tr><td><b>%1</b></td><td>%2</td></tr>")
  620. .arg(tr("LCSH"))
  621. .arg(m_module->getConfigEntry("LCSH"));
  622. text += QString("<tr><td><b>%1</b></td><td>%2</td></tr>")
  623. .arg(tr("Writable"))
  624. .arg(isWritable()? tr("yes") : tr("no"));
  625. if (isEncrypted())
  626. text += QString("<tr><td><b>%1</b></td><td>%2</td></tr>")
  627. .arg(tr("Unlock key"))
  628. .arg(config(CSwordModuleInfo::CipherKey));
  629. QString options;
  630. unsigned int opts;
  631. for (opts = CSwordModuleInfo::filterTypesMIN; opts <= CSwordModuleInfo::filterTypesMAX; ++opts) {
  632. if (has(static_cast < CSwordModuleInfo::FilterTypes > (opts))) {
  633. if (!options.isEmpty()) {
  634. options += QString::fromLatin1(", ");
  635. }
  636. options += CSwordBackend::translatedOptionName(static_cast < CSwordModuleInfo::FilterTypes > (opts));
  637. }
  638. }
  639. if (!options.isEmpty()) {
  640. text += QString("<tr><td><b>%1</b></td><td>%2</td></tr>")
  641. .arg(tr("Features"))
  642. .arg(options);
  643. }
  644. text += "</table><hr>";
  645. if (category() == Cult) //clearly say the module contains cult/questionable materials
  646. text += QString("<br/><b>%1</b><br/><br/>")
  647. .arg(tr("Take care, this work contains cult / questionable material!"));
  648. text += QString("<b>%1:</b><br>%2</font>")
  649. .arg(tr("About"))
  650. .arg(config(AboutInformation));
  651. typedef QList<CSwordModuleInfo::ConfigEntry> ListConfigEntry;
  652. ListConfigEntry entries;
  653. entries.append(DistributionLicense);
  654. entries.append(DistributionSource);
  655. entries.append(DistributionNotes);
  656. entries.append(TextSource);
  657. entries.append(CopyrightNotes);
  658. entries.append(CopyrightHolder);
  659. entries.append(CopyrightDate);
  660. entries.append(CopyrightContactName);
  661. entries.append(CopyrightContactAddress);
  662. entries.append(CopyrightContactEmail);
  663. typedef QMap<CSwordModuleInfo::ConfigEntry, QString> MapConfigEntry;
  664. MapConfigEntry entryMap;
  665. entryMap[DistributionLicense] = tr("Distribution license");
  666. entryMap[DistributionSource] = tr("Distribution source");
  667. entryMap[DistributionNotes] = tr("Distribution notes");
  668. entryMap[TextSource] = tr("Text source");
  669. entryMap[CopyrightNotes] = tr("Copyright notes");
  670. entryMap[CopyrightHolder] = tr("Copyright holder");
  671. entryMap[CopyrightDate] = tr("Copyright date");
  672. entryMap[CopyrightContactName] = tr("Copyright contact name");
  673. entryMap[CopyrightContactAddress] = tr("Copyright contact address");
  674. entryMap[CopyrightContactEmail] = tr("Copyright contact email");
  675. text += ("<hr><table>");
  676. for (ListConfigEntry::iterator it(entries.begin()); it != entries.end(); ++it) {
  677. QString t( config(*it) );
  678. if (!t.isEmpty()) {
  679. text += QString("<tr><td><b>%1</b></td><td>%2</td></tr>")
  680. .arg(entryMap[*it])
  681. .arg(config(*it));
  682. }
  683. }
  684. text += "</table></font>";
  685. return text;
  686. }
  687. /** Returns the language of the module. */
  688. const CLanguageMgr::Language * const CSwordModuleInfo::language() const {
  689. if (!m_dataCache.language) {
  690. if (module()) {
  691. if (category() == Glossary) {
  692. //special handling for glossaries, we use the "from language" as language for the module
  693. m_dataCache.language = (CPointers::languageMgr())->languageForAbbrev(config(GlossaryFrom));
  694. }
  695. else {
  696. m_dataCache.language = (CPointers::languageMgr())->languageForAbbrev(module()->Lang());
  697. }
  698. }
  699. else {
  700. m_dataCache.language = (CPointers::languageMgr())->defaultLanguage(); //default language
  701. }
  702. }
  703. return m_dataCache.language;
  704. }
  705. /*!
  706. \fn CSwordModuleInfo::getSimpleConfigEntry(char* name)
  707. */
  708. QString CSwordModuleInfo::getSimpleConfigEntry(const QString& name) const {
  709. QString ret = isUnicode()
  710. ? QString::fromUtf8(m_module->getConfigEntry(name.toUtf8().constData()))
  711. : QString::fromLatin1(m_module->getConfigEntry(name.toUtf8().constData()));
  712. return ret.isEmpty() ? QString::null : ret;
  713. }
  714. QString CSwordModuleInfo::getFormattedConfigEntry(const QString& name) const {
  715. sword::SWBuf RTF_Buffer(m_module->getConfigEntry(name.toUtf8().constData()));
  716. sword::RTFHTML RTF_Filter;
  717. RTF_Filter.processText(RTF_Buffer, 0, 0);
  718. QString ret = isUnicode()
  719. ? QString::fromUtf8(RTF_Buffer.c_str())
  720. : QString::fromLatin1(RTF_Buffer.c_str());
  721. return ret.isEmpty() ? QString::null : ret;
  722. }
  723. void CSwordModuleInfo::setHidden(bool hidden)
  724. {
  725. //qDebug("CSwordModuleInfo::setHidden");
  726. QStringList hiddenModules = CBTConfig::get(CBTConfig::hiddenModules);
  727. if (hidden && !hiddenModules.contains(this->name())) {
  728. hiddenModules.append(this->name());
  729. CBTConfig::set(CBTConfig::hiddenModules, hiddenModules);
  730. }
  731. if (!hidden && hiddenModules.contains(this->name()) ) {
  732. hiddenModules.removeAll(this->name());
  733. CBTConfig::set(CBTConfig::hiddenModules, hiddenModules);
  734. }
  735. }
  736. bool CSwordModuleInfo::isHidden() const
  737. {
  738. //qDebug("CSwordModuleInfo::isHidden");
  739. QStringList hiddenModules = CBTConfig::get(CBTConfig::hiddenModules);
  740. if (hiddenModules.contains(this->name())) {
  741. return true;
  742. }
  743. return false;
  744. }