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()
|