diff --git a/.gitattributes b/.gitattributes index 6af25fa..2a1bf4e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -23,6 +23,9 @@ fixtures/** filter=lfs diff=lfs merge=lfs -text # The manual's pictures live in LFS for the same reason the models do: a # screenshot or a GIF changes wholesale when the interface it shows changes, -# and every re-recording would otherwise stay in every clone for good. CI's -# pulls exclude the directory; nothing built or tested reads it. +# and every re-recording would otherwise stay in every clone for good. The +# desktop and benchmark legs exclude the directory, since nothing they build +# or test reads it; the Android and Windows legs fetch it, because the APK +# and the installer carry the manual (docs/manual/index.html) with its +# pictures, and their packagers refuse a pointer. docs/manual/media/** filter=lfs diff=lfs merge=lfs -text diff --git a/.gitea/workflows/build-and-test.yml b/.gitea/workflows/build-and-test.yml index 14fabf6..80a287b 100644 --- a/.gitea/workflows/build-and-test.yml +++ b/.gitea/workflows/build-and-test.yml @@ -100,7 +100,9 @@ jobs: | while read -r key; do git config --local --unset-all "$key"; done || true git config --local lfs.url \ "https://x-access-token:${LFS_TOKEN}@gitea.tourolle.paris/dtourolle/DarkRoom.git/info/lfs" - git lfs pull --exclude="fixtures/**,docs/manual/media/**" + # The manual's pictures too: the APK carries the manual, and + # assemble-apk.sh refuses a pointer where a picture should be. + git lfs pull --exclude="fixtures/**" ls -lR models/ - name: Cache cargo @@ -227,7 +229,9 @@ jobs: | while read -r key; do git config --local --unset-all "$key"; done || true git config --local lfs.url \ "https://x-access-token:${LFS_TOKEN}@gitea.tourolle.paris/dtourolle/DarkRoom.git/info/lfs" - git lfs pull --exclude="fixtures/**,docs/manual/media/**" + # The manual's pictures too: the APK carries the manual, and + # assemble-apk.sh refuses a pointer where a picture should be. + git lfs pull --exclude="fixtures/**" ls -lR models/ - name: Cache cargo @@ -420,7 +424,9 @@ jobs: | while read -r key; do git config --local --unset-all "$key"; done || true git config --local lfs.url \ "https://x-access-token:${LFS_TOKEN}@gitea.tourolle.paris/dtourolle/DarkRoom.git/info/lfs" - git lfs pull --exclude="fixtures/**,docs/manual/media/**" + # The manual's pictures too: the installer carries the manual, and + # package.sh refuses a pointer where a picture should be. + git lfs pull --exclude="fixtures/**" ls -l models/face models/scene - name: Cache cargo @@ -474,6 +480,11 @@ jobs: WANT=$(find models/face models/scene models/inpaint -maxdepth 1 -type f ! -name README.md | wc -l) GOT=$(ls "$INST/models" | wc -l) [ "$GOT" = "$WANT" ] || { echo "FAIL: expected $WANT model files, installed $GOT"; exit 1; } + # The manual, and every picture it shows, counted the same way. + [ -f "$INST/manual/index.html" ] || { echo "FAIL: no manual installed"; exit 1; } + WANT=$(ls docs/manual/media | wc -l) + GOT=$(ls "$INST/manual/media" | wc -l) + [ "$GOT" = "$WANT" ] || { echo "FAIL: expected $WANT manual pictures, installed $GOT"; exit 1; } wine reg query 'HKCU\Software\Microsoft\Windows\CurrentVersion\Uninstall\DarkRoom' 2>/dev/null \ | grep -q DisplayVersion || { echo "FAIL: no uninstall registry key"; exit 1; } wine "$INST/darkroom.exe" --version 2>/dev/null | grep -q '^darkroom-desktop ' \ diff --git a/docker/android/assemble-apk.sh b/docker/android/assemble-apk.sh index c0f6db9..1261abc 100755 --- a/docker/android/assemble-apk.sh +++ b/docker/android/assemble-apk.sh @@ -332,6 +332,29 @@ else echo " assets: no models found (face indexing and the scene tab will be off on the device)" fi +# The manual: the rendered page and its pictures, read in place by +# ManualActivity's WebView as file:///android_asset/manual/index.html. Not +# unpacked like the models: a WebView reads an asset straight out of the APK, +# and relative links to media/ resolve inside the same asset tree, so the page +# costs no first-launch copy and no second copy on /data. +# +# About 27 MB, stored below like the models — a GIF or PNG is already +# compressed, and deflating it again buys nothing. The pictures are LFS +# objects, and unlike a missing model a pointer would not fail loudly: it +# ships as a manual full of broken images. So it stops the build here. +rm -rf "${OUT}/staging/assets/manual" +mkdir -p "${OUT}/staging/assets/manual/media" +cp "${REPO}/docs/manual/index.html" "${OUT}/staging/assets/manual/" +for f in "${REPO}/docs/manual/media"/*; do + if head -c 40 "${f}" | grep -q '^version https://git-lfs'; then + echo "error: $(basename "${f}") is an LFS pointer, not a picture." >&2 + echo " run: git lfs pull --include='docs/manual/media/**'" >&2 + exit 1 + fi + cp "${f}" "${OUT}/staging/assets/manual/media/" +done +echo " manual: index.html and $(ls "${OUT}/staging/assets/manual/media" | wc -l) picture(s), $(du -sh "${OUT}/staging/assets/manual" | cut -f1)" + # -0 "" stores the .so without compression so Android can mmap it directly # (extractNativeLibs=false territory); for a 37 MB library that also keeps # install times sane. diff --git a/docker/windows/package.sh b/docker/windows/package.sh index ebe3766..d3a3045 100755 --- a/docker/windows/package.sh +++ b/docker/windows/package.sh @@ -69,6 +69,23 @@ for dir in face scene inpaint; do done echo "==> staged $(ls "${STAGE}/models" | wc -l) model file(s)" +# The manual: the rendered page and its pictures, beside the executable where +# dr_ui::manual looks on Windows (dr_plat::system_data_dirs is the exe's own +# directory there). The pictures are LFS objects; a pointer is ~130 bytes of +# text that every browser draws as a broken image, so refuse it here rather +# than ship a manual with no pictures in it. +mkdir -p "${STAGE}/manual/media" +cp "${REPO}/docs/manual/index.html" "${STAGE}/manual/" +for f in "${REPO}/docs/manual/media"/*; do + if head -c 40 "${f}" | grep -q '^version https://git-lfs'; then + echo "error: $(basename "${f}") is an LFS pointer, not a picture." >&2 + echo " run: git lfs pull --include='docs/manual/media/**'" >&2 + exit 1 + fi + cp "${f}" "${STAGE}/manual/media/" +done +echo "==> staged the manual and $(ls "${STAGE}/manual/media" | wc -l) picture(s)" + # One installer in the output directory, the one just built. The directory # is cached between CI runs, so after a version bump a glob over it would find # two and the smoke test would hand Wine both names as one path. diff --git a/docs/dev/windows.md b/docs/dev/windows.md index e25604d..b61fa7c 100644 --- a/docs/dev/windows.md +++ b/docs/dev/windows.md @@ -285,6 +285,8 @@ $LOCALAPPDATA\Programs\DarkRoom\ scrfd_500m_640.int8.onnx scrfd_2.5g_640.int8.onnx scrfd_10g_640.int8.onnx yolo26s-sem-ade20k.onnx yolo26s-sem-ade20k.classes.json categories.txt migan-512.onnx + manual\ + index.html media\ (the rendered manual and its pictures) LICENSE uninstall.exe ``` diff --git a/packaging/PKGBUILD b/packaging/PKGBUILD index 81880a0..cfe45db 100644 --- a/packaging/PKGBUILD +++ b/packaging/PKGBUILD @@ -59,6 +59,21 @@ package() { install -Dm644 "README.md" "${pkgdir}/usr/share/doc/${pkgname}/README.md" + # The manual: the rendered page and its pictures, where the app's Help + # opens it (dr_ui::manual, through dr_plat::system_data_dirs). Offline by + # design — the help sheet's "See it" links land here, on a machine that + # may have no network. The pictures are in LFS like the models, and a + # pointer shipped in their place is a manual of broken images, so the + # same refusal applies. + install -Dm644 "docs/manual/index.html" "${pkgdir}/usr/share/darkroom/manual/index.html" + for _f in docs/manual/media/*; do + if head -c 40 "${_f}" | grep -q '^version https://git-lfs'; then + echo "error: $(basename "${_f}") is an LFS pointer, not a picture — run: git lfs pull" >&2 + return 1 + fi + install -Dm644 "${_f}" "${pkgdir}/usr/share/darkroom/manual/media/$(basename "${_f}")" + done + # The face models, into the last directory the app searches. A pair the # user placed in their own data directory outranks these, so installing # them cannot override a deliberate choice of weights. diff --git a/packaging/windows/darkroom.nsi b/packaging/windows/darkroom.nsi index 05c97d8..e84952d 100644 --- a/packaging/windows/darkroom.nsi +++ b/packaging/windows/darkroom.nsi @@ -6,7 +6,8 @@ ; ; makensis -DVERSION=0.12.0 -DSTAGE=/path/to/staging -DOUT=/path/to/setup.exe darkroom.nsi ; -; STAGE holds exactly what §5.2 installs: darkroom.exe, models\ and LICENSE. +; STAGE holds exactly what §5.2 installs: darkroom.exe, models\, manual\ and +; LICENSE. ; package.sh assembles it and applies the LFS-pointer guard before this runs. ; A 64-bit installer, not NSIS's default 32-bit stub. The application is @@ -76,6 +77,13 @@ Section "DarkRoom" SecMain SetOutPath "$INSTDIR\models" File /r "${STAGE}\models\*" + ; The manual and its pictures, opened by Help and by the help sheet's + ; "See it" links (dr_ui::manual). Beside the executable for the same reason + ; the models are: it is the one directory the application can find without + ; asking the registry. + SetOutPath "$INSTDIR\manual" + File /r "${STAGE}\manual\*" + WriteUninstaller "$INSTDIR\uninstall.exe" ; Add/Remove Programs. HKCU, to match the per-user install. @@ -114,6 +122,7 @@ Section "Uninstall" Delete "$INSTDIR\LICENSE" Delete "$INSTDIR\uninstall.exe" RMDir /r "$INSTDIR\models" + RMDir /r "$INSTDIR\manual" RMDir "$INSTDIR" Delete "$SMPROGRAMS\${NAME}\${NAME}.lnk"