diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/libbible.cc | 262 | ||||
| -rw-r--r-- | src/lib/libbible.h | 115 | ||||
| -rw-r--r-- | src/lib/mods.cc | 233 | ||||
| -rw-r--r-- | src/lib/settings.cc | 23 | 
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()); +}  | 
