Skip to content

Instantly share code, notes, and snippets.

@AntumDeluge
Last active October 29, 2023 01:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AntumDeluge/80b4877db9afe1a2af259c3210d87b00 to your computer and use it in GitHub Desktop.
Save AntumDeluge/80b4877db9afe1a2af259c3210d87b00 to your computer and use it in GitHub Desktop.
A script for retrieving the current Stendhal test client.
#!/usr/bin/env python
# This file is licensed under CC0.
#
# A script for retrieving the Stendhal Java client.
#
# This script requires Python to be installed on your system.
# Tested with Python versions 2.7, 3.6, & 3.7. If you would like
# download progress displayed, install the "wget" module. The
# "wget" module can be installed via Pypi:
# `pip install wget` or `python -m pip install wget`
import errno, os, re, shutil, stat, subprocess, sys, zipfile
from platform import system
from subprocess import PIPE, STDOUT
py_legacy = sys.version_info.major <= 2
if not py_legacy:
import urllib.request as urllib2
from urllib.error import HTTPError
raw_input = input
module_error = ModuleNotFoundError
else:
import commands, urllib2
from urllib2 import HTTPError
module_error = ImportError
try:
import wget
except module_error:
print("WARNING:\twget module not found!")
print("\t\tDownload progress information will not be available.")
print("\t\tIf you would like progress info, install the wget\n\t\tmodule via Pypi:")
print("\t\t\t`pip install wget` or `python -m pip install wget`\n")
# system information
win32 = (system().lower() == "windows")
# script information
script_version = "1.3"
script_name = os.getenv("SCRIPT_NAME") or os.path.basename(__file__)
## Prints version information.
def showVersion():
print(script_version)
## Prints usage information.
def showUsage():
print("\nDescription:\n" \
+ " Script to retrieve current Stendhal testing client.\n" \
+ "\nVersion:\n" \
+ " {}\n".format(script_version) \
+ "\nUsage:\n" \
+ " {} [<options>] [<version>]\n".format(script_name) \
+ "\nArguments:\n" \
+ " options:\n" \
+ " -h|--help ..... Show this help information.\n" \
+ " -v|--version .. Show version information.\n" \
+ " -c|--clean .... Clean files associated with Stendhal's\n" \
+ " client from the extract directory\n" \
+ " (default: current directory).\n" \
+ " -e|--extract .. Directory where contents of download\n" \
+ " should be extracted (default: current\n" \
+ " directory).\n" \
+ " version:\n" \
+ " The target version. If this is omitted, the script will\n" \
+ " try to detect the latest version from Stendhal's GitHub\n" \
+ " repository.")
args = sys.argv[1:]
unknown_args = []
params = {
"show_u": False,
"show_v": False,
"version": None,
"testclient": False,
"clean": False,
"d_download": None,
"d_extract": os.getcwd(),
}
def argParamError(p):
print("\nERROR: `{}` parameter requires an argument".format(p))
sys.exit(1)
while len(args) > 0:
a = args[0].lower()
if a in ("-h", "--help", "help", "-?", "/?"):
params["show_u"] = True
elif a in ("-v", "--version"):
params["show_v"] = True
elif a in ("-t", "--testclient"):
params["testclient"] = True
elif a in ["-c", "--clean", "clean"]:
params["clean"] = True
elif a in ["-d", "--download"]:
if len(args) < 2 or args[1].startswith("-"):
argParamError("download")
params["d_download"] = args[1]
args.pop(1)
elif a in ["-e", "--extract"]:
if len(args) < 2 or args[1].startswith("-"):
argParamError("extract")
params["d_extract"] = args[1]
args.pop(1)
elif not params["version"]:
params["version"] = a
else:
unknown_args.append(a)
args.pop(0)
if len(unknown_args):
print("\nERROR: Unknown argument: {}".format(unknown_args[0]))
sys.exit(1)
if params["show_u"]:
showUsage()
sys.exit(0)
if params["show_v"]:
showVersion()
sys.exit(0)
if params["clean"]:
cont = (raw_input("\nWARNING: This will delete files from the" \
+ " directory ({}). Continue? [y/N]: ".format(params["d_extract"])) \
.lower() in ("y", "yes"))
if not cont:
print("Exiting ...")
sys.exit(0)
clean_files = ("stendhal-starter.jar", "stendhal-starter.exe", "icon.png", "README.md")
clean_exts = ("zip")
clean_dirs = ("doc", "lib", "log", "launch4j")
rmfiles = []
rmdirs = []
for filename in os.listdir(params["d_extract"]):
filepath = os.path.join(params["d_extract"], filename)
if os.path.isfile(filepath):
if filename in clean_files:
rmfiles.append(filepath)
continue
if "." in filename:
ext = filename.split(".")[-1]
if ext in clean_exts:
rmfiles.append(filepath)
continue
if os.path.isdir(filepath):
if filename in clean_dirs:
rmdirs.append(filepath)
if rmfiles or rmdirs:
print("Cleaning files & directories ...")
for filepath in rmfiles:
print("DEL:\t {}".format(filepath))
os.remove(filepath)
for dirpath in rmdirs:
print("DEL DIR: {}".format(dirpath))
shutil.rmtree(dirpath)
else:
print("No files or directories found to be cleaned.")
sys.exit(0)
## Converts the value of an environment variable to a tuple.
#
# Delimited by ";" on Windows, ":" otherwise.
#
# @param e
# Environment variable name to be expanded.
# @return
# Tuple.
def envToList(e):
e = os.getenv(e)
if not e:
return ()
if win32:
return tuple(e.split(";"))
return tuple(e.split(":"))
## Retrieves the first usable path from a list.
#
# @param default
# The path to use if none found from <code>group</code>.
# @param group
# List of paths to be checked for usability.
# @return
# The first usable path or <code>default</code>.
def selectWritableDir(default, group):
for d in group:
if d:
d = os.path.normpath(d)
if os.access(d, os.W_OK):
return d
default = os.path.normpath(default)
if not os.access(default, os.W_OK):
print("WARNING: using non-writable default directory: {}".format(default))
return default
## Checks write permissions of directory tree.
#
# @param d
# Directory tree to be checked.
def checkWriteTree(d, action="download"):
while d.strip() and not os.path.isdir(d):
d = os.path.dirname(d)
if not os.access(d, os.W_OK):
print("\nERROR: Access not granted to {} to {}".format(action, d))
sys.exit(errno.EPERM)
## Checks if a command is available.
#
# @param cmd
# The executable basename to search for.
# @return
# The command name if found.
def which(cmd):
if os.access(cmd, os.X_OK):
return cmd
# search PATH
PATH = envToList("PATH")
PATHEXT = envToList("PATHEXT")
for d in PATH:
filepath = os.path.join(d, cmd)
if os.access(filepath, os.X_OK):
return filepath
for ext in PATHEXT:
if not ext.startswith("."):
ext = "." + ext
for e in (ext, ext.lower(), ext.upper()):
filepath_ext = filepath + e
if os.access(filepath_ext, os.X_OK):
return filepath_ext
return None
usable_dirs = (
os.getenv("HOME"),
os.getenv("HOMEPATH"),
os.getenv("USERPROFILE"),
)
dir_home = selectWritableDir(os.path.expanduser("~"), usable_dirs)
# wget lib takes precedence
have_lib_wget = "wget" in globals()
cmd_wget = None
if not have_lib_wget:
cmd_wget = which("wget")
## Downloads a file from a URL.
#
# @param url
# The URL to fetch from.
# @param target
# The target download directory.
def download(url, target=None):
if not target:
print("\nERROR: must specify a download target")
sys.exit(1)
dir_parent = os.path.dirname(target)
if not os.path.isdir(dir_parent):
os.makedirs(dir_parent)
if have_lib_wget:
try:
wget.download(url, target)
print("\n")
except HTTPError:
print("\nERROR: Resource for Stendhal client version {} not found on server. Exiting ...".format(params["version"]))
sys.exit(errno.ENOENT)
elif cmd_wget != None:
# there are some problems with showing progress info with the wget executable
#execute_cmd = [cmd_wget, "--show-progress", url]
execute_cmd = [cmd_wget, "-q", url]
if target:
execute_cmd.insert(len(execute_cmd) - 1, "-O")
execute_cmd.insert(len(execute_cmd) - 1, target)
if py_legacy:
ps = subprocess.Popen(execute_cmd, stderr=subprocess.PIPE)
ps.communicate()
output = None
exit_res = ps.returncode
else:
exit_res, output = subprocess.getstatusoutput(" ".join(execute_cmd))
if exit_res != 0:
# remove invalid zip created by wget
if os.path.isfile(target):
os.remove(target)
print("\nERROR: Could not retrieve resource for Stendhal client version {} (return code {}). No other information available. Exiting ...".format(params["version"], exit_res))
sys.exit(exit_res)
else:
# either wget command nor library available
print("\nwget library and wget executable not found. Using built-in Python libraries for downloading.")
file_name = os.path.basename(url)
if target == None:
target = os.getcwd()
try:
dl_open = urllib2.urlopen(url)
dl_data = dl_open.read()
dl_open.close()
if os.path.isdir(target):
target = os.path.join(target, file_name)
fopen = open(target, "wb")
fopen.write(dl_data)
fopen.close()
except HTTPError:
print("\nERROR: Resource for Stendhal client version {} not found on server. Exiting ...".format(params["version"]))
sys.exit(errno.ENOENT)
if not params["version"]:
ant_prop_url = "https://raw.githubusercontent.com/arianne/stendhal/master/build.ant.properties"
print("Stendhal version not specified, attempting to retrieve automatically from URL:\n\t\t{}".format(ant_prop_url))
try:
prop_open = urllib2.urlopen(ant_prop_url)
prop = prop_open.read().decode("utf-8").split("\n");
prop_open.close();
vdelim = "version"
if not params["testclient"]:
vdelim = vdelim + ".old"
for line in prop:
line = re.sub(" ", "", line)
if line.startswith(vdelim + "="):
params["version"] = line.split("=")[1]
if not params["version"]:
print("\nERROR:\n" \
+ " Could not parse version information from URL:\n" \
+ " {}".format(ant_prop_url) + "\n" \
+ " Please specify version number manually: {} <version>".format(script_name))
sys.exit(1)
except HTTPError:
print("\nERROR:\n" \
+ " Could not retrieve version information from URL:\n" \
+ " {}".format(ant_prop_url) + "\n" \
+ " Please specify version number manually: {} <version>".format(script_name))
sys.exit(1)
file_zip = "stendhal-{}.zip".format(params["version"])
if params["testclient"]:
client_url = "https://stendhalgame.org/download/{}".format(file_zip)
else:
client_url = "https://sourceforge.net/projects/arianne/files/stendhal/{0}/{1}/download".format(params["version"], file_zip)
# set up download directory (use current directory if no other useable target)
useable_dirs = [
os.getenv("TMP"),
os.getenv("TEMP"),
"/tmp",
"{}/Downloads".format(dir_home),
"{}/Downloads".format(os.getenv("USERPROFILE"))]
if not params["d_download"]:
params["d_download"] = selectWritableDir(os.getcwd(), useable_dirs)
checkWriteTree(params["d_download"])
checkWriteTree(params["d_extract"], "extract")
file_download = os.path.join(params["d_download"], file_zip)
print("\nVersion: {}\n".format(params["version"]) \
+ "Source: {}\n".format(client_url) \
+ "Target: {}\n".format(file_download))
## Gets prompt to overwrite download target .zip archive.
def overwriteDownloadTargetZip(url, target):
cont = True
fexists = os.path.isfile(target)
if fexists:
cont = (raw_input("Overwrite existing file? [y/N]: ").lower() in ("y", "yes"))
if cont:
if not os.access(target, os.W_OK):
print("\nERROR: Access not granted to delete file {}".format(target))
sys.exit(errno.EPERM)
os.remove(target)
if cont:
print("Downloading file from {}".format(url))
download(url, target)
elif fexists:
cont = raw_input("Extract contents anyway? [y/N]: ").lower() in ("y", "yes")
return cont
## Extracts a .zip archive.
#
# @param source
# File path.
def extractZip(source):
if not os.path.isfile(source):
print("ERROR: File not found: {}".format(source))
return False
print("Extracting to: {}".format(params["d_extract"]))
try:
archive = zipfile.ZipFile(source, "r")
archive.extractall(params["d_extract"])
archive.close()
except zipfile.BadZipfile:
print("\nERROR: .zip file is corrupt: {}".format(source))
sys.exit(errno.EBADFD)
# get icon
ico = "{}/icon.png".format(params["d_extract"])
if not os.path.isfile(ico):
print("Downloading icon ...")
download("https://github.com/arianne/stendhal/raw/master/data/gui/StendhalIcon.png", ico)
return True
# download & extract Stendhal client .zip
if overwriteDownloadTargetZip(client_url, file_download):
extractZip(file_download)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment