tarted work on restoring Apple @e support.
This commit is contained in:
@@ -79,6 +79,33 @@ download_with_fallback() {
|
||||
return 1
|
||||
}
|
||||
|
||||
internet_available() {
|
||||
local targetUrl
|
||||
local targetUrls=(
|
||||
"https://www.gnu.org/"
|
||||
"https://1.1.1.1/"
|
||||
)
|
||||
|
||||
for targetUrl in "${targetUrls[@]}"; do
|
||||
if curl -fsS --connect-timeout 5 --max-time 8 "$targetUrl" > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
require_internet_connection() {
|
||||
local message="No internet connection detected. Configure a connection from System Settings, Internet Configuration, then try again."
|
||||
|
||||
if internet_available; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
ui_msgbox "Game Installer" "Game Installer" "$message"
|
||||
return 1
|
||||
}
|
||||
|
||||
get_remote_file_size() {
|
||||
local sourceUrl="$1"
|
||||
local headerOutput
|
||||
|
||||
@@ -69,6 +69,8 @@ trap finish_logging EXIT
|
||||
# shellcheck disable=SC1091
|
||||
source "${scriptDir}/game_install_functions.sh"
|
||||
|
||||
require_internet_connection
|
||||
|
||||
speak "Installing ${gameName}"
|
||||
# shellcheck source=/dev/null
|
||||
source "${installerPath}"
|
||||
|
||||
@@ -7,9 +7,157 @@ import os
|
||||
import sys
|
||||
import time
|
||||
import curses
|
||||
import hashlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
import speechd # Python bindings for Speech Dispatcher
|
||||
from stormux_speech_settings import load_speech_settings, save_speech_settings
|
||||
|
||||
|
||||
APPLE2E_CID = "QmY9fUfpexakGuq6MGn3pSLpNJxy8jW7rU79GGywBSekHY"
|
||||
APPLE2E_ZIP_NAME = "apple2e.zip"
|
||||
APPLE2E_ZIP_SHA256 = "5632c11d86c173f9e2a686e31542c16aafd1cc9ef3b6a3bcb5ff44a19bdd1195"
|
||||
APPLE2E_SUPPORT_DIR = Path.home() / ".local" / "games" / "apple2e"
|
||||
APPLE2E_CACHE_DIR = Path.home() / ".cache" / "stormux"
|
||||
APPLE2E_BOOT_DISK = "Echo II - Textalker DOS 3.3.dsk"
|
||||
|
||||
|
||||
def speak_message(message, wait=False):
|
||||
flag = "-Cw" if wait else "-C"
|
||||
subprocess.run(["spd-say", flag, message], check=False)
|
||||
|
||||
|
||||
def internet_available():
|
||||
"""Return True when outbound HTTPS appears available."""
|
||||
targets = ("https://www.gnu.org/", "https://1.1.1.1/")
|
||||
for target in targets:
|
||||
result = subprocess.run(
|
||||
["curl", "-fsS", "--connect-timeout", "5", "--max-time", "8", target],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
check=False,
|
||||
)
|
||||
if result.returncode == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def apple_support_url():
|
||||
gateway = os.environ.get("ipfsGateway", "https://ipfs.stormux.org")
|
||||
return f"{gateway}/ipfs/{APPLE2E_CID}?filename={APPLE2E_ZIP_NAME}"
|
||||
|
||||
|
||||
def sha256_file(filePath):
|
||||
digest = hashlib.sha256()
|
||||
with open(filePath, "rb") as fileHandle:
|
||||
for chunk in iter(lambda: fileHandle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def download_file(sourceUrl, outputPath):
|
||||
subprocess.run(
|
||||
[
|
||||
"curl",
|
||||
"--fail",
|
||||
"--show-error",
|
||||
"--location",
|
||||
"--connect-timeout",
|
||||
"20",
|
||||
"--retry",
|
||||
"3",
|
||||
"--retry-delay",
|
||||
"2",
|
||||
"--output",
|
||||
str(outputPath),
|
||||
sourceUrl,
|
||||
],
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def apple_support_installed(supportDir=APPLE2E_SUPPORT_DIR):
|
||||
supportDir = Path(supportDir)
|
||||
return (
|
||||
(supportDir / APPLE2E_BOOT_DISK).is_file()
|
||||
and (supportDir / "disks").is_dir()
|
||||
and any((supportDir / "disks").glob("*.dsk"))
|
||||
)
|
||||
|
||||
|
||||
def validate_zip_member(memberName):
|
||||
memberPath = Path(memberName)
|
||||
return (
|
||||
not memberPath.is_absolute()
|
||||
and ".." not in memberPath.parts
|
||||
and len(memberPath.parts) >= 1
|
||||
and memberPath.parts[0] == "apple2e"
|
||||
)
|
||||
|
||||
|
||||
def extract_apple_support_archive(zipPath, supportDir):
|
||||
supportDir = Path(supportDir)
|
||||
gamesDir = supportDir.parent
|
||||
gamesDir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with zipfile.ZipFile(zipPath) as archive:
|
||||
memberNames = archive.namelist()
|
||||
if not all(validate_zip_member(name) for name in memberNames):
|
||||
raise RuntimeError("Apple 2e support archive contains unsafe paths.")
|
||||
if f"apple2e/{APPLE2E_BOOT_DISK}" not in memberNames:
|
||||
raise RuntimeError("Apple 2e support archive is missing the boot disk.")
|
||||
if not any(name.startswith("apple2e/disks/") and name.lower().endswith(".dsk") for name in memberNames):
|
||||
raise RuntimeError("Apple 2e support archive is missing disk images.")
|
||||
|
||||
tempRoot = Path(tempfile.mkdtemp(prefix="apple2e_extract_", dir=str(gamesDir)))
|
||||
try:
|
||||
archive.extractall(tempRoot)
|
||||
extractedDir = tempRoot / "apple2e"
|
||||
if supportDir.exists():
|
||||
shutil.rmtree(supportDir)
|
||||
shutil.move(str(extractedDir), str(supportDir))
|
||||
(supportDir / ".stormux-installed").write_text("installed\n")
|
||||
finally:
|
||||
shutil.rmtree(tempRoot, ignore_errors=True)
|
||||
|
||||
|
||||
def ensure_apple_support_files(
|
||||
supportDir=APPLE2E_SUPPORT_DIR,
|
||||
cacheDir=APPLE2E_CACHE_DIR,
|
||||
speak=speak_message,
|
||||
):
|
||||
supportDir = Path(supportDir)
|
||||
cacheDir = Path(cacheDir)
|
||||
if apple_support_installed(supportDir):
|
||||
return True
|
||||
|
||||
if not internet_available():
|
||||
message = (
|
||||
"No internet connection detected. Configure a connection from "
|
||||
"System Settings, Internet Configuration, then open Apple 2e again."
|
||||
)
|
||||
print(message)
|
||||
speak(message, True)
|
||||
return False
|
||||
|
||||
cacheDir.mkdir(parents=True, exist_ok=True)
|
||||
zipPath = cacheDir / APPLE2E_ZIP_NAME
|
||||
|
||||
speak("Apple 2e support files are not installed. Downloading them now.", True)
|
||||
download_file(apple_support_url(), zipPath)
|
||||
|
||||
if sha256_file(zipPath) != APPLE2E_ZIP_SHA256:
|
||||
zipPath.unlink(missing_ok=True)
|
||||
raise RuntimeError("Apple 2e support archive failed checksum verification.")
|
||||
|
||||
extract_apple_support_archive(zipPath, supportDir)
|
||||
speak("Apple 2e support files installed.", True)
|
||||
return True
|
||||
|
||||
|
||||
class VoicedDiskMenu:
|
||||
def __init__(self, title="Apple 2e Disk Menu"):
|
||||
self.title = title
|
||||
@@ -363,6 +511,9 @@ class VoicedDiskMenu:
|
||||
|
||||
# Run the menu
|
||||
if __name__ == "__main__":
|
||||
if not ensure_apple_support_files():
|
||||
sys.exit(1)
|
||||
|
||||
# Create the menu
|
||||
menu = VoicedDiskMenu()
|
||||
|
||||
|
||||
@@ -33,6 +33,31 @@ progress_beep() {
|
||||
fi
|
||||
}
|
||||
|
||||
internet_available() {
|
||||
local targetUrl
|
||||
local targetUrls=(
|
||||
"https://www.gnu.org/"
|
||||
"https://1.1.1.1/"
|
||||
)
|
||||
|
||||
for targetUrl in "${targetUrls[@]}"; do
|
||||
if curl -fsS --connect-timeout 5 --max-time 8 "$targetUrl" > /dev/null 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
require_internet_connection() {
|
||||
if internet_available; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
speak "No internet connection detected. Configure a connection from System Settings, Internet Configuration, then try again."
|
||||
return 1
|
||||
}
|
||||
|
||||
# Download with progress feedback
|
||||
download_with_progress() {
|
||||
local url="$1"
|
||||
@@ -192,6 +217,8 @@ auto_install() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
require_internet_connection || return 1
|
||||
|
||||
# Download and install
|
||||
local temp_zip="/tmp/${directory}_download.zip"
|
||||
if download_with_progress "$url" "$temp_zip" "$name"; then
|
||||
@@ -233,6 +260,8 @@ auto_install() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
require_internet_connection || return 1
|
||||
|
||||
# Install package
|
||||
if install_system_package "$package" "$name"; then
|
||||
speak "Installation complete"
|
||||
|
||||
Reference in New Issue
Block a user