File size: 9,553 Bytes
850d678
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71403c6
 
 
 
 
 
 
 
 
 
 
 
850d678
 
71403c6
 
 
 
850d678
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
#!/usr/bin/env python
r"""YahBible launcher -- the entry point PyInstaller freezes into YahBible.exe.



Out of the box: double-click. On first run it downloads the study library from Hugging Face

(OhBeOneKeyNoBe/YahBible) into a local folder, then starts the local server and opens the app

in your browser. Subsequent runs are instant. Works run-in-place OR installed -- the library

lives in %LOCALAPPDATA%\YahBible so it persists either way.



The library mirrors the app's expected layout under one BASE dir, and YAHBIBLE_BASE points the

server at it (the server falls back to D:\ only on the dev machine).

"""
import os
import sys
import threading
import time
import webbrowser

REPO = "OhBeOneKeyNoBe/YahBible"
PORT = 41537
# study library: HF path  ->  subpath under BASE (mirrors the app's expected tree)
LIB = {
    "data/watchman.db": "watchman/watchman.db",
    "data/reflected_red_basic.sqlite": "Holorites_data/daeos/reflected_red_basic.sqlite",
    "data/reflected_red.sqlite": "Holorites_data/reflected_red.sqlite",
    "data/taviel_versions.sqlite": "Holorites_data/daeos/taviel_versions.sqlite",
    "data/taviel_corpus.sqlite": "Holorites_data/daeos/taviel_corpus.sqlite",
    "data/english_dict.sqlite": "Holorites_data/daeos/english_dict.sqlite",
    "data/piper/en_US-lessac-medium.onnx": "Holorites_data/daeos/piper/en_US-lessac-medium.onnx",
    "data/piper/en_US-lessac-medium.onnx.json": "Holorites_data/daeos/piper/en_US-lessac-medium.onnx.json",
    "data/reflected_vocal.sqlite": "Holorites/torus_upgrades/reflected_vocal.sqlite",
    "data/elaniel_yt.sqlite": "Holorites_data/daeos/elaniel_yt.sqlite",
    "data/taviel_redletter.sqlite": "Holorites_data/daeos/taviel_redletter.sqlite",
    "data/taviel_doctrine.sqlite": "Holorites_data/daeos/taviel_doctrine.sqlite",
}
# the big lexicon (interlinear + Strong's + dictionary). Optional first-run: it is 70GB, so we
# fetch it in the background after the app is already usable, unless YAHBIBLE_SKIP_LEX is set.
BIG = "data/reflected_red.sqlite"

# --- CODE auto-update -------------------------------------------------------
# The app CODE (server + UI + study data) lives in the HF repo under code/ and is pulled on every
# launch so fixes reach users WITHOUT shipping a new exe. Downloaded modules are loaded over the
# frozen copies; any failure falls back to the frozen build, so the app always starts.
CODE_FILES = ["o_taviel_ui.py", "o_taviel_server.py",
              "taviel_commandments.json", "taviel_repentance.json", "taviel_news.json",
              # the Gauntlet: served by o_taviel_server, so installed desktops pull these too
              "taviel_apologetics.py", "taviel_reason.py", "gauntlet_kb.json",
              "taviel_doctrine.py",
              # THE JEV FRONT DOOR. taviel_reason.serve() routes through these
              # when the decision server and the embedder are both reachable,
              # answering identity, vetted, verse, clarify and decline with no
              # generated tokens at all. Uploading them to the repo was not
              # enough: this list is what an installed desktop actually
              # fetches, so a module missing from here is a module that never
              # arrives, and the front door would sit in the repo doing
              # nothing. They are SAFE to ship to a desktop with no fork
              # binary -- serve() gates on a cheap probe and falls back to the
              # verbatim gauntlet exactly as before.
              "taviel_frontdoor.py", "taviel_decide.py", "taviel_person.py",
              "taviel_contrast.py", "taviel_verify.py",
              # completed interlinear for verses the lexicon lacks (KJV<->MT versification
              # divergences + NT doxologies) -- so installed desktops match web/mobile
              "canon_fill.sqlite",
              # BlackBox split-key auth: o_taviel_server imports these to publish the two
              # encrypted credential halves (HF + GitHub) on signup / password change
              "yahbible_auth.py", "auth_sync.py"]


def ensure_code(base):
    cdir = os.path.join(base, "code")
    os.makedirs(cdir, exist_ok=True)
    try:
        from huggingface_hub import hf_hub_download
    except Exception:
        return cdir
    import shutil
    for fn in CODE_FILES:
        for _ in range(3):
            try:
                cached = hf_hub_download(repo_id=REPO, filename="code/" + fn, repo_type="model")
                tmp = os.path.join(cdir, fn + ".tmp")
                shutil.copy2(cached, tmp)
                os.replace(tmp, os.path.join(cdir, fn))
                break
            except Exception as e:
                print("  code sync skip %s (%s)" % (fn, str(e)[:40]), flush=True)
                time.sleep(2)
    return cdir


def load_updated_code(cdir):
    """Load the freshly-downloaded o_taviel_ui / o_taviel_server over the frozen copies."""
    import importlib.util
    for mod in ("o_taviel_ui", "o_taviel_server"):
        p = os.path.join(cdir, mod + ".py")
        if not (os.path.exists(p) and os.path.getsize(p) > 0):
            continue
        try:
            spec = importlib.util.spec_from_file_location(mod, p)
            m = importlib.util.module_from_spec(spec)
            sys.modules[mod] = m
            spec.loader.exec_module(m)
            print("  updated %s loaded from repo" % mod, flush=True)
        except Exception as e:
            print("  updated %s failed, using built-in (%s)" % (mod, str(e)[:60]), flush=True)
            sys.modules.pop(mod, None)


def base_dir():
    b = os.environ.get("YAHBIBLE_BASE")
    if b:
        return b
    root = os.environ.get("LOCALAPPDATA") or os.path.expanduser("~")
    b = os.path.join(root, "YahBible", "library")
    os.makedirs(b, exist_ok=True)
    return b


def have(base, sub):
    p = os.path.join(base, sub.replace("/", os.sep))
    return os.path.exists(p) and os.path.getsize(p) > 0


def fetch(base, hf_path, sub):
    from huggingface_hub import hf_hub_download
    dst = os.path.join(base, sub.replace("/", os.sep))
    os.makedirs(os.path.dirname(dst), exist_ok=True)
    print("  downloading %s ..." % sub, flush=True)
    cached = hf_hub_download(repo_id=REPO, filename=hf_path, repo_type="model")
    # copy out of the HF cache into our mirrored layout (hardlink if possible)
    import shutil
    try:
        if os.path.exists(dst):
            os.remove(dst)
        os.link(cached, dst)
    except Exception:
        shutil.copy2(cached, dst)


def ensure_library(base):
    small = {k: v for k, v in LIB.items() if k != BIG}
    missing = [(k, v) for k, v in small.items() if not have(base, v)]
    if missing:
        print("First run: fetching the YahBible study library (~1.2 GB) ...", flush=True)
        for k, v in missing:
            for _ in range(4):
                try:
                    fetch(base, k, v)
                    break
                except Exception as e:
                    print("   retry (%s)" % str(e)[:60], flush=True)
                    time.sleep(4)
    # the 70GB lexicon: fetch in the background so the app is usable immediately
    if not have(base, LIB[BIG]) and not os.environ.get("YAHBIBLE_SKIP_LEX"):
        def _bg():
            for _ in range(6):
                try:
                    print("Background: fetching the full lexicon (interlinear + Strong's, ~70 GB)...", flush=True)
                    fetch(base, BIG, LIB[BIG])
                    print("Lexicon ready -- interlinear + Strong's now enabled.", flush=True)
                    break
                except Exception as e:
                    print("   lexicon retry (%s)" % str(e)[:60], flush=True)
                    time.sleep(10)
        threading.Thread(target=_bg, daemon=True).start()


def main():
    # when frozen, our bundled modules sit next to the exe / in the PyInstaller temp
    here = os.path.dirname(os.path.abspath(sys.argv[0]))
    if getattr(sys, "_MEIPASS", None):
        here = sys._MEIPASS
    if here not in sys.path:
        sys.path.insert(0, here)
    base = base_dir()
    os.environ["YAHBIBLE_BASE"] = base
    print("YahBible -- library at:", base, flush=True)
    ensure_library(base)
    # pull the latest app code from the repo so fixes apply without a new exe
    try:
        cdir = ensure_code(base)
        os.environ["YAHBIBLE_CODE"] = cdir      # so the server's _res() finds updated study data
        if cdir not in sys.path:
            sys.path.insert(0, cdir)
        load_updated_code(cdir)
    except Exception as e:
        print("  code auto-update skipped (%s)" % str(e)[:60], flush=True)
    # start the server (import after YAHBIBLE_BASE is set so its path constants resolve)
    import o_taviel_server as S
    threading.Thread(target=S.main, daemon=True).start()
    # wait for it to bind, then open the browser
    import socket
    for _ in range(120):
        s = socket.socket(); s.settimeout(0.4)
        try:
            s.connect(("127.0.0.1", PORT)); s.close(); break
        except Exception:
            time.sleep(0.5)
    webbrowser.open("http://127.0.0.1:%d/" % PORT)
    print("YahBible is running at http://127.0.0.1:%d/  (close this window to quit)" % PORT, flush=True)
    try:
        while True:
            time.sleep(3600)
    except KeyboardInterrupt:
        pass


if __name__ == "__main__":
    main()