aboutsummaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/libbible.cc262
-rw-r--r--src/lib/libbible.h115
-rw-r--r--src/lib/mods.cc233
-rw-r--r--src/lib/settings.cc23
4 files changed, 633 insertions, 0 deletions
diff --git a/src/lib/libbible.cc b/src/lib/libbible.cc
new file mode 100644
index 0000000..c9acb7d
--- /dev/null
+++ b/src/lib/libbible.cc
@@ -0,0 +1,262 @@
+#include "libbible.h"
+#include <sword/versekey.h>
+#include <sword/markupfiltmgr.h>
+#include <sword/swmodule.h>
+#include <sword/swmgr.h>
+#include <sword/osisfootnotes.h>
+#include <algorithm>
+
+using namespace sword;
+using namespace std;
+
+SWMgr library(new MarkupFilterMgr(FMT_XHTML));
+OSISFootnotes filter;
+
+vector<string> getBooks(SWModule *target) {
+ vector<string> books;
+ VerseKey *key = (VerseKey *) target->getKey();
+ for(char t = 1; t <= key->getTestamentMax(); t++) {
+ key->setTestament(t);
+ for(char b = 1; b <= key->getBookMax(); b++) {
+ key->setBook(b);
+ // Bug (whose fault??) in JPS; they CLAIM to have two testaments,
+ // but they only have one, which causes repeats.
+ if(std::find(books.begin(), books.end(), key->getBookName()) != books.end()) {
+ continue;
+ }
+ // Another issue (maybe bug?) Some translations are NT only,
+ // but still report OT books/chapters.
+ if(string(target->renderText()).empty()) {
+ continue;
+ }
+ books.push_back(key->getBookName());
+ }
+ }
+ return books;
+}
+
+map<string, vector<string>> libbible::getModules() {
+ library.load();
+ map<string, vector<string>> mods;
+ ModMap::iterator it;
+ for (it = library.getModules().begin(); it != library.getModules().end(); it++) {
+ string modName = (*it).second->getName();
+ SWModule *target = library.getModule(modName.c_str());
+ mods[modName] = getBooks(target);
+ }
+ return mods;
+}
+
+vector<libbible::passage> libbible::getPassages(string modName, string book) {
+ vector<libbible::passage> passages;
+ SWModule *target = library.getModule(modName.c_str());
+ if(target == nullptr) {
+ // Module doesn't exist
+ return passages;
+ }
+ target->setKey((book + " " + "1").c_str());
+ VerseKey *key = (VerseKey *) target->getKey();
+ int maxChapter = key->getChapterMax();
+ for(int chapter = 1; chapter <= maxChapter; chapter++) {
+ string ref = book + ' ' + to_string(chapter);
+ target->setKey(ref.c_str());
+ VerseKey *key = (VerseKey *) target->getKey();
+ libbible::passage pass;
+ pass.modName = modName;
+ pass.book = string(key->getBookName());
+ pass.bookShort = string(key->getBookAbbrev());
+ pass.chapterStart = chapter;
+ pass.chapterEnd = chapter;
+ pass.verseStart = 1;
+ pass.verseEnd = key->getVerseMax();
+ passages.push_back(pass);
+ }
+ return passages;
+}
+
+libbible::text getEmptyText(VerseKey *key) {
+ libbible::text t;
+ t.chapter = key->getChapter();
+ t.verse = key->getVerse();
+ t.book = key->getBookName();
+ t.bookShort = key->getBookAbbrev();
+ return t;
+}
+
+libbible::passage libbible::getPassage(string modName, string reference) {
+ libbible::passage pass;
+ pass.modName = modName;
+ SWModule *target = library.getModule(pass.modName.c_str());
+ if(target == nullptr || reference.empty()) {
+ // Bad input
+ return pass;
+ }
+ vector<string> validBooks = getBooks(target);
+ //printf("Hey, I'm inferring missing parts!\n");
+ // Let's use the target to help us
+ target->setKey(reference.c_str());
+ VerseKey *key = (VerseKey *) target->getKey();
+ pass.book = string(key->getBookName());
+ // Hold on a moment, is this book even legal?
+ if(find(validBooks.begin(), validBooks.end(), pass.book) == validBooks.end()) {
+ key->setBookName(validBooks[0].c_str());
+ pass.book = string(key->getBookName());
+ }
+ pass.bookShort = string(key->getBookAbbrev());
+ pass.chapterStart = key->getChapter();
+ pass.verseStart = key->getVerse();
+ //printf("Results so far: book: %s; chapterStart: %d; verseStart: %d\n", pass.book.c_str(), pass.chapterStart, pass.verseStart);
+ // And now we just need chapterEnd and verseEnd. Yippee.
+ string ref = string(reference);
+ ref.erase(remove(ref.begin(), ref.end(), ' '), ref.end());
+ if(ref.find('-') == string::npos) {
+ // There's no range!
+ if(ref.find(':') == string::npos) {
+ // It's a full chapter reference
+ pass.chapterEnd = pass.chapterStart;
+ pass.verseEnd = key->getVerseMax();
+ } else {
+ // It's a single verse reference
+ pass.chapterEnd = pass.chapterStart;
+ pass.verseEnd = pass.verseStart;
+ //printf("Hey, it's a single verse reference!\n");
+ }
+ } else {
+ if(ref.find(':') == string::npos) {
+ // It's a multi-full-chapter reference
+ pass.chapterEnd = stoi(ref.substr(ref.find_last_of('-')+1));
+ key->setChapter(pass.chapterEnd);
+ pass.verseEnd = key->getVerseMax();
+ } else {
+ // It falls in categories c:v-v or c:v-c:v (or, technically, c-c:v)
+ string rangeEnd = ref.substr(ref.find_last_of('-')+1);
+ if(rangeEnd.find(':') == string::npos) {
+ // It's c:v-v
+ pass.verseEnd = stoi(rangeEnd);
+ pass.chapterEnd = pass.chapterStart;
+ } else {
+ // It's c:v-c:v (or c-c:v, but code is the same)
+ pass.chapterEnd = stoi(rangeEnd.substr(0, rangeEnd.find(':')));
+ pass.verseEnd = stoi(rangeEnd.substr(rangeEnd.find(':')+1));
+ }
+ }
+ }
+ return pass;
+}
+
+vector<libbible::text> libbible::getText(libbible::passage pass) {
+ vector<libbible::text> texts;
+ SWModule *target = library.getModule(pass.modName.c_str());
+ filter.setOptionValue("Off");
+ target->addOptionFilter(&filter);
+ if(target == nullptr) {
+ // Module doesn't exist
+ return texts;
+ }
+ if(pass.book.empty()) {
+ pass.book = pass.bookShort;
+ }
+ target->setKey((pass.book
+ + " " + to_string(pass.chapterStart)
+ + ":" + to_string(pass.verseStart)).c_str());
+ VerseKey *key = (VerseKey *) target->getKey();
+
+ bool endOfParagraph = false;
+
+ string book = string(key->getBookName());
+
+ for(; string(key->getBookName()) == book &&
+ (key->getChapter() < pass.chapterEnd
+ || (key->getChapter() == pass.chapterEnd && key->getVerse() <= pass.verseEnd));
+ (*key)++) {
+
+ string text = string(target->renderText());
+ //printf("Working with: %s\n", text.c_str());
+
+ texts.push_back(getEmptyText(key));
+
+ if(key->getVerse() == 1 || endOfParagraph) {
+ if(find(texts.back().modifiers.begin(), texts.back().modifiers.end(), "paragraph") == texts.back().modifiers.end()) {
+ texts.back().modifiers.push_back("paragraph");
+ }
+ endOfParagraph = false;
+ }
+
+ // Variable to accumulate unterminated spans
+ std::vector<std::pair<std::string, std::string>> spans;
+ bool spansChanged = false;
+ bool hasAddedText = false;
+ // Iterate over text
+ for(auto i = text.begin(); i != text.end(); i++) {
+ if(*i != '<') {
+ if(spansChanged) {
+ spansChanged = false;
+ if(!texts.back().text.empty()) {
+ texts.push_back(getEmptyText(key));
+ }
+ for(auto& [tag, modifier] : spans) {
+ if(find(texts.back().modifiers.begin(), texts.back().modifiers.end(), modifier) == texts.back().modifiers.end()) {
+ texts.back().modifiers.push_back(modifier);
+ }
+ }
+ }
+ if(*i == '\n') {
+ continue; // We add newlines with <br />
+ }
+ if(! hasAddedText && (*i == ' ' || *i == '\t')) {
+ continue;
+ }
+ if(*i == "¶"[0] && i+1 != text.end() && *(i+1) == "¶"[1]) {
+ i++;
+ if(hasAddedText) {
+ texts.back().text += '\n';
+ } else {
+ // Append \n to text in previous texts (if applicable)
+ if(texts.size() > 1) {
+ texts[texts.size()-2].text += '\n';
+ }
+ texts.back().modifiers.push_back("paragraph");
+ continue;
+ }
+ }
+ texts.back().text += *i;
+ hasAddedText = true;
+ }
+ else {
+ string span;
+ for(; i != text.end(); i++) {
+ span.push_back(*i);
+ if(*i == '>') {
+ // The end of the span will be "</tag>".
+ if(span[1] == '/') {
+ string tag = span.substr(2, span.size()-3);
+ for(auto rit = spans.rbegin(); rit != spans.rend(); rit++) {
+ if(rit->first == tag) {
+ spans.erase(rit.base()-1);
+ spansChanged = true;
+ break;
+ }
+ }
+ } else if(span.find("class=\"") != string::npos) {
+ // The span will be formatted "<tag class=\"NAME\">"
+ // We want just the NAME
+ string tag = span.substr(1, span.find(" ")-1);
+ size_t start = span.find("class=\"")+7;
+ size_t end = span.find("\"", start);
+ spans.push_back(std::pair<string, string>(tag, span.substr(start, end-start)));
+ spansChanged = true;
+ } else if(span.find("preverse") != string::npos) {
+ string tag = span.substr(1, span.find(" ")-1);
+ spans.push_back(std::pair<string, string>(tag, "preverse"));
+ } else if(span == "<br />" || span == "<br/>") {
+ texts.back().text += '\n';
+ }
+ break;
+ }
+ }
+ }
+ }
+ endOfParagraph = (text[text.length()-1] == '\n');
+ }
+ return texts;
+}
diff --git a/src/lib/libbible.h b/src/lib/libbible.h
new file mode 100644
index 0000000..f77dc8c
--- /dev/null
+++ b/src/lib/libbible.h
@@ -0,0 +1,115 @@
+#include <string>
+#include <vector>
+#include <map>
+
+namespace libbible {
+
+ struct text {
+ int chapter;
+ int verse;
+ std::string book;
+ std::string bookShort;
+ std::string text;
+ std::vector<std::string> modifiers; // e.g., paragraph, line indent0, divineName, wordsOfJesus
+ };
+
+ struct passage {
+ std::string modName;
+ std::string book;
+ std::string bookShort;
+ int chapterStart;
+ int verseStart;
+ int chapterEnd;
+ int verseEnd;
+ };
+
+ /*
+ * @return Map of modName to supported books
+ */
+ std::map<std::string, std::vector<std::string>> getModules(void);
+
+ /*
+ * @return Vector of valid single full-chapter passages for a book
+ */
+ std::vector<struct passage> getPassages(std::string modName, std::string book);
+
+ /*
+ * @param modName the module to use for determining the passage
+ * @param reference a human-readable reference, e.g., "gen 1:26-27"
+ * @return the passage matching the reference
+ */
+ passage getPassage(std::string modName, std::string reference);
+
+ /*
+ * @return Text for a passage
+ */
+ std::vector<struct text> getText(struct passage pass);
+
+ /**************************
+ * Methods dealing with mods
+ ***************************/
+
+ class Status {
+ public:
+ virtual void update(unsigned long totalBytes, unsigned long completedBytes, std::string message) {}
+ };
+
+ /**
+ * @param status Status update method is called asynchronously as download progresses
+ */
+ void setStatusReporter(Status& status);
+
+ /**
+ * @return A mapping from language to bible version names
+ */
+ std::map<std::string, std::vector<std::string>> downloadModsAvailable();
+
+ /**
+ * @return A mapping from language abbreviations to full language names
+ */
+ std::map<std::string, std::string> getLanguageNames();
+
+ /**
+ * Cancel an in-progress download
+ */
+ void terminateDownload(void);
+
+ /**
+ * @param language The language of the mod to install as provided from downloadModsAvailable
+ * @param name The name of the bible version as provided from downloadModsAvailable
+ * @see downloadModsAvailable()
+ * @return true on success, false otherwise
+ */
+ bool installModFromInternet(std::string language, std::string name);
+
+ /**
+ * @param filename Path to the .zip compressed module to be installed
+ * @return true on success, false otherwise
+ */
+ bool installModFromZip(std::string filename);
+
+ /**
+ * @param modname The name of the module to be removed
+ */
+ void uninstallMod(std::string modname);
+
+ /******************************
+ * Methods dealing with settings
+ *******************************/
+
+ /*
+ * From already established code, valid and useful values are:
+ * int fontsize: the last used size of the font
+ * string passage: the last looked-up passage
+ * string module: the last used module
+ */
+
+ void settingsWrite(std::string key, std::string value);
+
+ std::string settingsRead(std::string key);
+
+ void settingsWriteInt(std::string key, int value);
+
+ int settingsReadInt(std::string key);
+
+}
diff --git a/src/lib/mods.cc b/src/lib/mods.cc
new file mode 100644
index 0000000..ab54e48
--- /dev/null
+++ b/src/lib/mods.cc
@@ -0,0 +1,233 @@
+#include "libbible.h"
+#include <sword/swmgr.h>
+#include <sword/swmodule.h>
+#include <sword/installmgr.h>
+#include <sword/filemgr.h>
+#include <sword/remotetrans.h>
+#include <unzip.h>
+#include <filesystem>
+
+using namespace std;
+
+class myStatusReporter : public sword::StatusReporter {
+ public:
+ myStatusReporter(libbible::Status *status);
+ ~myStatusReporter();
+ void preStatus(long totalBytes, long completedBytes, const char *message);
+ void update(unsigned long totalBytes, unsigned long completedBytes);
+ protected:
+ libbible::Status *status;
+ string message;
+};
+
+myStatusReporter::myStatusReporter(libbible::Status *s) {
+ status = s;
+}
+
+myStatusReporter::~myStatusReporter() {};
+
+//virtual void libbible::Status::update(unsigned long totalBytes, unsigned long completedBytes, string message) {}
+
+void myStatusReporter::preStatus(long totalBytes, long completedBytes, const char *msg) {
+ message = string(msg);
+ status->update((unsigned long) totalBytes, (unsigned long) completedBytes, message);
+ //printf("Got a status update: %ld / %ld, \"%s\"\n", completedBytes, totalBytes, message.c_str());
+}
+
+void myStatusReporter::update(unsigned long totalBytes, unsigned long completedBytes) {
+ status->update(totalBytes, completedBytes, message);
+ //printf("Got a status update: %ld / %ld, \"%s\"\n", completedBytes, totalBytes, message.c_str());
+}
+
+string basedir = (getenv("HOME")) + string("/.sword/");
+sword::InstallMgr *installMgr = new sword::InstallMgr((basedir + std::string("InstallMgr")).c_str(), nullptr);
+map<string, vector<pair<string, sword::InstallSource *>>> installSources;
+map<string, string> languageNames; // maps abbreviation to full name
+
+void libbible::setStatusReporter(libbible::Status& status) {
+ myStatusReporter *msr = new myStatusReporter(&status);
+ free(installMgr);
+ installMgr = new sword::InstallMgr((basedir + std::string("InstallMgr")).c_str(), msr);
+ installMgr->setUserDisclaimerConfirmed(true);
+}
+
+map<string, vector<string>> libbible::downloadModsAvailable() {
+ installSources.clear();
+ languageNames.clear();
+ mkdir((basedir + std::string("mods.d/")).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ mkdir((basedir + std::string("modules/")).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
+ installMgr->setUserDisclaimerConfirmed(true);
+ string confpath = basedir + string("InstallMgr/InstallMgr.conf");
+ if(! sword::FileMgr::existsFile(confpath.c_str())) {
+ // Lifted directly from xiphos
+ sword::FileMgr::createParent(confpath.c_str());
+ sword::SWConfig config(confpath.c_str());
+ sword::InstallSource is("FTP");
+ is.caption = "CrossWire";
+ is.source = "ftp.crosswire.org";
+ is.directory = "/pub/sword/raw";
+ config["General"]["PassiveFTP"] = "true";
+ config["Sources"]["FTPSource"] = is.getConfEnt();
+ config.save();
+ installMgr->refreshRemoteSourceConfiguration();
+ }
+ installMgr->readInstallConf();
+ map<string, vector<string>> modsAvailable;
+ map<string, vector<string>> languagesToFull;
+ //printf("Getting langs...\n");
+ for(auto src : installMgr->sources) {
+ if(src.second->getMgr()->Modules.empty()) {
+ //printf("Refreshing remote source: %s\n", src.second->getConfEnt().c_str());
+ installMgr->refreshRemoteSource(src.second);
+ }
+ for(auto mod : src.second->getMgr()->Modules) {
+ auto *curMod = mod.second;
+ string type(curMod->getType());
+ if(type == "Biblical Texts") {
+ string language(curMod->getLanguage());
+ string fullLang;
+ if(curMod->getConfigEntry("LCSH")) {
+ // Split on periods, last field, strip
+ fullLang = string(curMod->getConfigEntry("LCSH"));
+ // If ends with ., remove
+ if(fullLang.ends_with('.')) fullLang = fullLang.substr(0, fullLang.size()-1);
+ if(fullLang.find('.') != string::npos) fullLang = fullLang.substr(fullLang.find_last_of('.')+1);
+ while(fullLang.starts_with(' ')) fullLang = fullLang.substr(1);
+ while(fullLang.ends_with(' ')) fullLang = fullLang.substr(0, fullLang.size()-1);
+ }
+ vector<string> newLangs;
+ languagesToFull.emplace(language, newLangs);
+ languagesToFull[language].push_back(fullLang);
+ vector<string> newMods;
+ vector<pair<string, sword::InstallSource *>> newSources;
+ // emplace only adds if key is unique
+ modsAvailable.emplace(language, newMods);
+ installSources.emplace(language, newSources);
+ modsAvailable[language].push_back(string(curMod->getName()));
+ pair<string, sword::InstallSource *> p(string(curMod->getName()), src.second);
+ installSources[language].push_back(p);
+ }
+ }
+ }
+ // Now use majority voting to move languagesToFull -> languageNames
+ for(const auto& [abbrev, fulls] : languagesToFull) {
+ std::map<string, int> majVote;
+ for(auto full : fulls) {
+ majVote.try_emplace(full, 0);
+ majVote[full]++;
+ }
+ string selected = fulls[0];
+ for(auto full : fulls) {
+ if(majVote[full] > majVote[selected] or (majVote[full] == majVote[selected] and !full.empty() and full.size() < selected.size())) {
+ selected = full;
+ }
+ }
+ if(selected.empty()) languageNames[abbrev] = abbrev;
+ else languageNames[abbrev] = selected;
+ }
+ return modsAvailable;
+}
+
+std::map<std::string, std::string> libbible::getLanguageNames() {
+ if(languageNames.empty()) {
+ downloadModsAvailable();
+ }
+ return languageNames;
+}
+
+void libbible::terminateDownload() {
+ installMgr->terminate();
+}
+
+bool libbible::installModFromInternet(string language, string name) {
+ // Searching through map<string, vector<pair<string, sword::InstallSource *>>> installSources;
+ if(installSources.empty()) {
+ downloadModsAvailable();
+ }
+ for (pair<string, sword::InstallSource *> p : installSources[language]) {
+ if(p.first == name) {
+ sword::SWMgr mgr(basedir.c_str());
+ if(installMgr->installModule(&mgr, 0, name.c_str(), p.second) == 0) {
+ printf("Installed from %s\n", p.second->getConfEnt().c_str());
+ return true;
+ }
+ return false;
+ }
+ }
+ return false;
+}
+
+#define READ_SIZE 8192
+#define delim '/'
+
+bool libbible::installModFromZip(string filename) {
+ // So... turns out it's a mite unsupported to install from a .zip
+ // Here's the deal. We do a syscall to unzip. We fancy like that.
+ // TODO: Use the ZipCompress module from SWORD instead.
+ /*string command = "unzip -o " + filename + " -d " + basedir + "&> /dev/null";
+ if(system(command.c_str())) {
+ //Uh oh...
+ printf("Something bad happened when unpacking %s\n. Is unzip installed?", filename.c_str());
+ }*/
+ unzFile zipfile = unzOpen(filename.c_str());
+ if(zipfile == NULL) {
+ return false;
+ }
+ unz_global_info global_info;
+ if(unzGetGlobalInfo(zipfile, &global_info) != UNZ_OK) {
+ unzClose(zipfile);
+ return false;
+ }
+ char read_buffer[READ_SIZE];
+ ulong i;
+ for(i = 0; i < global_info.number_entry; i++) {
+ unz_file_info file_info;
+ if(unzGetCurrentFileInfo(zipfile, &file_info, read_buffer, READ_SIZE, NULL, 0, NULL, 0) != UNZ_OK) {
+ unzClose(zipfile);
+ return false;
+ }
+ string fname = basedir + string(read_buffer);
+ size_t pos = fname.find_last_of(delim);
+ if(pos != string::npos) {
+ string path = fname.substr(0, pos);
+ filesystem::create_directories(path);
+ }
+ if(unzOpenCurrentFile(zipfile) != UNZ_OK) {
+ unzCloseCurrentFile(zipfile);
+ unzClose(zipfile);
+ return false;
+ }
+ FILE *out = fopen(fname.c_str(), "wb");
+ if(out == NULL) {
+ unzCloseCurrentFile(zipfile);
+ unzClose(zipfile);
+ return false;
+ }
+ int bytesRead;
+ do {
+ bytesRead = unzReadCurrentFile(zipfile, read_buffer, READ_SIZE);
+ if(bytesRead < 0) {
+ printf("error %d\n", bytesRead);
+ unzCloseCurrentFile(zipfile);
+ unzClose(zipfile);
+ return false;
+ }
+ if(bytesRead > 0) {
+ fwrite(read_buffer, bytesRead, 1, out);
+ }
+ } while(bytesRead > 0);
+ fclose(out);
+ unzCloseCurrentFile(zipfile);
+ unzGoToNextFile(zipfile);
+ }
+ unzClose(zipfile);
+ return true;
+}
+
+void libbible::uninstallMod(string modname) {
+ sword::SWMgr mgr(basedir.c_str());
+ sword::ModMap::iterator it = mgr.Modules.find(modname.c_str());
+ if(it != mgr.Modules.end()) {
+ installMgr->removeModule(&mgr, it->second->getName());
+ }
+}
diff --git a/src/lib/settings.cc b/src/lib/settings.cc
new file mode 100644
index 0000000..848e22f
--- /dev/null
+++ b/src/lib/settings.cc
@@ -0,0 +1,23 @@
+#include "libbible.h"
+#include <sword/swconfig.h>
+
+std::string path = (std::getenv("HOME")) + std::string("/.sword/libbible.conf");
+sword::SWConfig config(path.c_str());
+
+void libbible::settingsWrite(std::string key, std::string value) {
+ config["General"][key.c_str()] = sword::SWBuf(value.c_str());
+ config.save();
+}
+
+std::string libbible::settingsRead(std::string key) {
+ return config["General"][key.c_str()].c_str();
+}
+
+void libbible::settingsWriteInt(std::string key, int value) {
+ config["General"][key.c_str()] = sword::SWBuf(std::to_string(value).c_str());
+ config.save();
+}
+
+int libbible::settingsReadInt(std::string key) {
+ return atoi(config["General"][key.c_str()].c_str());
+}