Merge branch 'master' of git.zavage.net:Zavage-Software/smileyface

This commit is contained in:
Mathew Guest 2024-08-15 05:09:35 -06:00
commit ff06cc59eb
22 changed files with 867 additions and 404 deletions

11
.flake8 Normal file

@ -0,0 +1,11 @@
[flake8]
max-line-length=120
ignore =
E121,
E123,
E126,
E226,
E24,
E704,
W605
exclude = ./tests

1
.gitignore vendored

@ -3,4 +3,5 @@ build/
dist/
__pycache__
*.egg-info
idea

37
.pre-commit-config.yaml Normal file

@ -0,0 +1,37 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
- id: check-yaml
- id: check-added-large-files
args: ['--maxkb=10240']
- id: check-merge-conflict
- id: end-of-file-fixer
- id: trailing-whitespace
# isort -- sorts imports
- repo: https://github.com/timothycrosley/isort
rev: 5.13.2
hooks:
- id: isort
args: ["--profile", "black", "--filter-files"]
# Flake8
#- repo: https://github.com/pycqa/flake8
# rev: '7.0.0'
# hooks:
# - id: flake8
# Black
# Using this mirror lets us use mypyc-compiled black, which is about 2x faster
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 24.2.0
hooks:
- id: black
language_version: python3.8
# Poetry
- repo: https://github.com/python-poetry/poetry
rev: 1.8.2
hooks:
- id: poetry-lock

1
.python-version Normal file

@ -0,0 +1 @@
3.8.19

@ -1,6 +1,6 @@
SmileyFace Unreal Tournament 4 Hub Automator
Copyright (c) 2020 Mathew Guest
Copyright (c) 2024 Mathew Guest
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation

440
poetry.lock generated Normal file

@ -0,0 +1,440 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
[[package]]
name = "app-skellington"
version = "0.1.1"
description = "A high-powered command line menu framework."
optional = false
python-versions = ">=3"
files = [
{file = "app_skellington-0.1.1-py3-none-any.whl", hash = "sha256:61853a63b6683a3bccbca87c34d19352c1c5c4ab80fb4ff5fa77ebc0cf3348af"},
{file = "app_skellington-0.1.1.tar.gz", hash = "sha256:d9ea7423f8e9434724065e528f89b35182abe9b338e6997397d46e98e7f87a0a"},
]
[package.dependencies]
appdirs = "*"
colorlog = "*"
configobj = "*"
[[package]]
name = "appdirs"
version = "1.4.4"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
optional = false
python-versions = "*"
files = [
{file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"},
{file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"},
]
[[package]]
name = "black"
version = "24.4.2"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
{file = "black-24.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dd1b5a14e417189db4c7b64a6540f31730713d173f0b63e55fabd52d61d8fdce"},
{file = "black-24.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8e537d281831ad0e71007dcdcbe50a71470b978c453fa41ce77186bbe0ed6021"},
{file = "black-24.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eaea3008c281f1038edb473c1aa8ed8143a5535ff18f978a318f10302b254063"},
{file = "black-24.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:7768a0dbf16a39aa5e9a3ded568bb545c8c2727396d063bbaf847df05b08cd96"},
{file = "black-24.4.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:257d724c2c9b1660f353b36c802ccece186a30accc7742c176d29c146df6e474"},
{file = "black-24.4.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bdde6f877a18f24844e381d45e9947a49e97933573ac9d4345399be37621e26c"},
{file = "black-24.4.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e151054aa00bad1f4e1f04919542885f89f5f7d086b8a59e5000e6c616896ffb"},
{file = "black-24.4.2-cp311-cp311-win_amd64.whl", hash = "sha256:7e122b1c4fb252fd85df3ca93578732b4749d9be076593076ef4d07a0233c3e1"},
{file = "black-24.4.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:accf49e151c8ed2c0cdc528691838afd217c50412534e876a19270fea1e28e2d"},
{file = "black-24.4.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:88c57dc656038f1ab9f92b3eb5335ee9b021412feaa46330d5eba4e51fe49b04"},
{file = "black-24.4.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:be8bef99eb46d5021bf053114442914baeb3649a89dc5f3a555c88737e5e98fc"},
{file = "black-24.4.2-cp312-cp312-win_amd64.whl", hash = "sha256:415e686e87dbbe6f4cd5ef0fbf764af7b89f9057b97c908742b6008cc554b9c0"},
{file = "black-24.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:bf10f7310db693bb62692609b397e8d67257c55f949abde4c67f9cc574492cc7"},
{file = "black-24.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:98e123f1d5cfd42f886624d84464f7756f60ff6eab89ae845210631714f6db94"},
{file = "black-24.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48a85f2cb5e6799a9ef05347b476cce6c182d6c71ee36925a6c194d074336ef8"},
{file = "black-24.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1530ae42e9d6d5b670a34db49a94115a64596bc77710b1d05e9801e62ca0a7c"},
{file = "black-24.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:37aae07b029fa0174d39daf02748b379399b909652a806e5708199bd93899da1"},
{file = "black-24.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da33a1a5e49c4122ccdfd56cd021ff1ebc4a1ec4e2d01594fef9b6f267a9e741"},
{file = "black-24.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef703f83fc32e131e9bcc0a5094cfe85599e7109f896fe8bc96cc402f3eb4b6e"},
{file = "black-24.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:b9176b9832e84308818a99a561e90aa479e73c523b3f77afd07913380ae2eab7"},
{file = "black-24.4.2-py3-none-any.whl", hash = "sha256:d36ed1124bb81b32f8614555b34cc4259c3fbc7eec17870e8ff8ded335b58d8c"},
{file = "black-24.4.2.tar.gz", hash = "sha256:c872b53057f000085da66a19c55d68f6f8ddcac2642392ad3a355878406fbd4d"},
]
[package.dependencies]
click = ">=8.0.0"
mypy-extensions = ">=0.4.3"
packaging = ">=22.0"
pathspec = ">=0.9.0"
platformdirs = ">=2"
tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""}
typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
[[package]]
name = "cfgv"
version = "3.4.0"
description = "Validate configuration and produce human readable error messages."
optional = false
python-versions = ">=3.8"
files = [
{file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
{file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
]
[[package]]
name = "click"
version = "8.1.7"
description = "Composable command line interface toolkit"
optional = false
python-versions = ">=3.7"
files = [
{file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"},
{file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"},
]
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.6"
description = "Cross-platform colored terminal text."
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
]
[[package]]
name = "colorlog"
version = "6.8.2"
description = "Add colours to the output of Python's logging module."
optional = false
python-versions = ">=3.6"
files = [
{file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"},
{file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"},
]
[package.dependencies]
colorama = {version = "*", markers = "sys_platform == \"win32\""}
[package.extras]
development = ["black", "flake8", "mypy", "pytest", "types-colorama"]
[[package]]
name = "configobj"
version = "5.0.8"
description = "Config file reading, writing and validation."
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
files = [
{file = "configobj-5.0.8-py2.py3-none-any.whl", hash = "sha256:a7a8c6ab7daade85c3f329931a807c8aee750a2494363934f8ea84d8a54c87ea"},
{file = "configobj-5.0.8.tar.gz", hash = "sha256:6f704434a07dc4f4dc7c9a745172c1cad449feb548febd9f7fe362629c627a97"},
]
[package.dependencies]
six = "*"
[[package]]
name = "distlib"
version = "0.3.8"
description = "Distribution utilities"
optional = false
python-versions = "*"
files = [
{file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
]
[[package]]
name = "filelock"
version = "3.15.4"
description = "A platform independent file lock."
optional = false
python-versions = ">=3.8"
files = [
{file = "filelock-3.15.4-py3-none-any.whl", hash = "sha256:6ca1fffae96225dab4c6eaf1c4f4f28cd2568d3ec2a44e15a08520504de468e7"},
{file = "filelock-3.15.4.tar.gz", hash = "sha256:2207938cbc1844345cb01a5a95524dae30f0ce089eba5b00378295a17e3e90cb"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"]
typing = ["typing-extensions (>=4.8)"]
[[package]]
name = "flake8"
version = "5.0.4"
description = "the modular source code checker: pep8 pyflakes and co"
optional = false
python-versions = ">=3.6.1"
files = [
{file = "flake8-5.0.4-py2.py3-none-any.whl", hash = "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248"},
{file = "flake8-5.0.4.tar.gz", hash = "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db"},
]
[package.dependencies]
mccabe = ">=0.7.0,<0.8.0"
pycodestyle = ">=2.9.0,<2.10.0"
pyflakes = ">=2.5.0,<2.6.0"
[[package]]
name = "identify"
version = "2.6.0"
description = "File identification library for Python"
optional = false
python-versions = ">=3.8"
files = [
{file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"},
{file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"},
]
[package.extras]
license = ["ukkonen"]
[[package]]
name = "isort"
version = "5.13.2"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.8.0"
files = [
{file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
{file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
]
[package.extras]
colors = ["colorama (>=0.4.6)"]
[[package]]
name = "mccabe"
version = "0.7.0"
description = "McCabe checker, plugin for flake8"
optional = false
python-versions = ">=3.6"
files = [
{file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
{file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
]
[[package]]
name = "mypy-extensions"
version = "1.0.0"
description = "Type system extensions for programs checked with the mypy type checker."
optional = false
python-versions = ">=3.5"
files = [
{file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"},
{file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"},
]
[[package]]
name = "nodeenv"
version = "1.9.1"
description = "Node.js virtual environment builder"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
files = [
{file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"},
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
]
[[package]]
name = "packaging"
version = "24.1"
description = "Core utilities for Python packages"
optional = false
python-versions = ">=3.8"
files = [
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
]
[[package]]
name = "pathspec"
version = "0.12.1"
description = "Utility library for gitignore style pattern matching of file paths."
optional = false
python-versions = ">=3.8"
files = [
{file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"},
{file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"},
]
[[package]]
name = "platformdirs"
version = "4.2.2"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
optional = false
python-versions = ">=3.8"
files = [
{file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"},
{file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"},
]
[package.extras]
docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
type = ["mypy (>=1.8)"]
[[package]]
name = "pre-commit"
version = "3.5.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
python-versions = ">=3.8"
files = [
{file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"},
{file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"},
]
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
virtualenv = ">=20.10.0"
[[package]]
name = "pycodestyle"
version = "2.9.1"
description = "Python style guide checker"
optional = false
python-versions = ">=3.6"
files = [
{file = "pycodestyle-2.9.1-py2.py3-none-any.whl", hash = "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b"},
{file = "pycodestyle-2.9.1.tar.gz", hash = "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785"},
]
[[package]]
name = "pyflakes"
version = "2.5.0"
description = "passive checker of Python programs"
optional = false
python-versions = ">=3.6"
files = [
{file = "pyflakes-2.5.0-py2.py3-none-any.whl", hash = "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2"},
{file = "pyflakes-2.5.0.tar.gz", hash = "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3"},
]
[[package]]
name = "pyyaml"
version = "6.0.1"
description = "YAML parser and emitter for Python"
optional = false
python-versions = ">=3.6"
files = [
{file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
{file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
{file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
{file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
{file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
{file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
{file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
{file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
{file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
{file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
{file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
{file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
{file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
{file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
{file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
{file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
{file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
{file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
{file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
{file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
{file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
{file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
{file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
{file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
{file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
{file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
{file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
{file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
{file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
]
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
optional = false
python-versions = ">=3.7"
files = [
{file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"},
{file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"},
]
[[package]]
name = "typing-extensions"
version = "4.12.2"
description = "Backported and Experimental Type Hints for Python 3.8+"
optional = false
python-versions = ">=3.8"
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
]
[[package]]
name = "virtualenv"
version = "20.26.3"
description = "Virtual Python Environment builder"
optional = false
python-versions = ">=3.7"
files = [
{file = "virtualenv-20.26.3-py3-none-any.whl", hash = "sha256:8cc4a31139e796e9a7de2cd5cf2489de1217193116a8fd42328f1bd65f434589"},
{file = "virtualenv-20.26.3.tar.gz", hash = "sha256:4c43a2a236279d9ea36a0d76f98d84bd6ca94ac4e0f4a3b9d46d05e10fea542a"},
]
[package.dependencies]
distlib = ">=0.3.7,<1"
filelock = ">=3.12.2,<4"
platformdirs = ">=3.9.1,<5"
[package.extras]
docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "c2001f64c789fcc98f1fc1702a4a511e788776a0c7d4c438d66193f0a1241c0c"

51
pyproject.toml Normal file

@ -0,0 +1,51 @@
[tool.poetry]
name = "smileyface"
version = "0.1.0"
description = "smileyface UT4 hub automator hosting"
authors = [
"Mathew Guest <mat@zavage.net>",
]
license = "MIT"
readme = "README.md"
homepage = "https://zavage-software.com/portfolio/smileyface"
repository = "https://git-mirror.zavage.net/zavage-software/smileyface"
documentation = "https://git-mirror.zavage.net/zavage-software/smileyface"
keywords = ["cas"]
packages = [{ include = "smileyface" }]
include = [
"README.md",
]
# [tool.poetry.scripts]
[tool.poetry.dependencies]
python = "^3.8"
app_skellington = "*"
configobj = "*"
colorlog = "*"
appdirs = "*"
[tool.poetry.group.dev.dependencies]
black = "*"
pre-commit = "*"
isort = "*"
flake8 = "*"
#Sphinx = "^5.3.0"
#sphinx-rtd-theme = "^1.3.0"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.black]
line-length = 120
target-version = ['py38']
[tool.isort]
multi_line_output = 3
combine_as_imports = true
include_trailing_comma = true
force_grid_wrap = 3
ensure_newline_before_comments = true

@ -1,35 +1,25 @@
#!/usr/bin/env python
from setuptools import setup, find_packages
from setuptools import find_packages, setup
__project__ = 'SmileyFace UT4 Hub Automator'
__version__ = '0.1.0'
__project__ = "SmileyFace UT4 Hub Automator"
__version__ = "0.1.0"
app_skellington_requirements = (
'appdirs',
'colorlog',
'configobj',
"appdirs",
"colorlog",
"configobj",
)
setup(
name = __project__,
version = __version__,
description = 'Unreal Tournament 4 Server Admin and Control Panel',
author = 'Mathew Guest',
author_email = 't3h.zavage@gmail.com',
url = 'https://git-mirror.zavage-software.com',
name=__project__,
version=__version__,
description="Unreal Tournament 4 Server Admin and Control Panel",
author="Mathew Guest",
author_email="t3h.zavage@gmail.com",
url="https://git-mirror.zavage-software.com",
# Third-party dependencies; will be automatically installed
install_requires = (
'rdiff-backup',
'app_skellington',
'appdirs',
'sqlparse'
) + app_skellington_requirements,
packages = find_packages(),
package_dir = {
'app_skellington': 'lib/app_skellington'
},
install_requires=("rdiff-backup", "app_skellington", "appdirs", "sqlparse") + app_skellington_requirements,
packages=find_packages(),
package_dir={"app_skellington": "lib/app_skellington"},
)

@ -1,4 +1,4 @@
#!/usr/bin/env python
import smileyface
smileyface.start_app()
smileyface.start_app()

@ -2,18 +2,20 @@ import logging
import sys
# Module parameters and constants
APP_NAME = 'SmileyFace Unreal Tournament 4 Server Panel'
APP_AUTHOR = 'Mathew Guest'
APP_VERSION = '0.1.0'
APP_NAME = "SmileyFace Unreal Tournament 4 Server Panel"
APP_AUTHOR = "Mathew Guest"
APP_VERSION = "0.1.0"
APP_CONFIG_FILENAME = 'config.ini'
APP_CONFIG_FILENAME = "config.ini"
# config.spec is relative to the module src directory and is the
# config specification (structure, names, and types of config file)
APP_CONFIGSPEC_FILENAME = 'config.spec'
APP_CONFIGSPEC_FILENAME = "config.spec"
# Check and gracefully fail if the user needs to install a 3rd-party dep.
required_lib_names = ['appdirs', 'configobj', 'colorlog']
required_lib_names = ["appdirs", "configobj", "colorlog"]
def check_env_has_dependencies(required_lib_names):
"""
Attempts to import each module and gracefully fails if it doesn't
@ -24,15 +26,17 @@ def check_env_has_dependencies(required_lib_names):
try:
__import__(libname)
except ImportError as ex:
print('missing third-part library: ', ex, file=sys.stderr)
print("missing third-part library: ", ex, file=sys.stderr)
rc = False
except Exception as ex:
print(ex, type(ex))
rc = False
return rc
if not check_env_has_dependencies(required_lib_names):
print('refusing to load program without installed dependencies', file=sys.stderr)
raise ImportError('python environment needs third-party dependencies installed')
print("refusing to load program without installed dependencies", file=sys.stderr)
raise ImportError("python environment needs third-party dependencies installed")
# Exposed from sub-modules:
from .app import start_app

@ -1,11 +1,11 @@
import hashlib
import functools
import hashlib
def md5sum_file(filename):
with open(filename, mode='rb') as f:
with open(filename, mode="rb") as f:
d = hashlib.md5()
for buf in iter(functools.partial(f.read, 128), b''):
for buf in iter(functools.partial(f.read, 128), b""):
d.update(buf)
h = d.hexdigest()
return h

@ -1,63 +1,49 @@
from . import hub_machine
from . import datalayer
from . import scrape_latest
import app_skellington
from app_skellington import _util
from . import (
datalayer,
hub_machine,
scrape_latest,
)
class SmileyFace(app_skellington.ApplicationContainer):
def __init__(self, *args, **kwargs):
filename = 'config.spec'
filename = "config.spec"
self.configspec_filepath = _util.get_asset(__name__, filename)
config_filepath = self._get_config_filepath(
'smileyface-ut4',
'',
'hub-config.ini'
)
config_filepath = self._get_config_filepath("smileyface-ut4", "", "hub-config.ini")
super().__init__(
configspec_filepath=self.configspec_filepath,
configini_filepath=config_filepath,
app_name = 'SmileyFace UT4 Server Panel',
app_author = 'Mathew Guest',
app_version = '0.1',
app_name="SmileyFace UT4 Server Panel",
app_author="Mathew Guest",
app_version="0.1",
*args,
**kwargs
**kwargs,
)
def _cli_options(self):
pass
def _command_menu(self):
sm_root = self.cli.init_submenu('command')
_util.register_class_as_commands(
self, sm_root,
hub_machine.UT4ServerMachine
)
sm_root = self.cli.init_submenu("command")
_util.register_class_as_commands(self, sm_root, hub_machine.UT4ServerMachine)
sm_scrape = sm_root.create_submenu('scrape')
_util.register_class_as_commands(
self, sm_scrape,
scrape_latest.ScrapeUt4Pugs
)
sm_scrape = sm_root.create_submenu("scrape")
_util.register_class_as_commands(self, sm_scrape, scrape_latest.ScrapeUt4Pugs)
_util.register_class_as_commands(
self, sm_scrape,
scrape_latest.ScrapeUtcc
)
_util.register_class_as_commands(self, sm_scrape, scrape_latest.ScrapeUtcc)
_util.register_class_as_commands(
self, sm_scrape,
scrape_latest.LocalFs
)
_util.register_class_as_commands(self, sm_scrape, scrape_latest.LocalFs)
def _services(self):
self['model'] = lambda: hub_machine.UTServerMachine(self.ctx)
self["model"] = lambda: hub_machine.UTServerMachine(self.ctx)
self.dal = datalayer.DataLayer(self.ctx)
self['dal'] = lambda: self.dal
self['datalayer'] = lambda: datalayer.DbFuncs(self.ctx, self.dal)
self["dal"] = lambda: self.dal
self["datalayer"] = lambda: datalayer.DbFuncs(self.ctx, self.dal)
# self['localfs'] = lambda: datalayer.LocalFs(self.ctx, datalayer)
@ -67,7 +53,7 @@ class SmileyFace(app_skellington.ApplicationContainer):
def invoke_from_cli(self):
rc = self.load_command()
if not rc:
print('Invalid command. Try -h for usage')
print("Invalid command. Try -h for usage")
return
# load config
self.invoke_command()
@ -142,4 +128,3 @@ Typical Usage:
def start_app():
app = SmileyFace()
app.invoke_from_cli()

@ -1,8 +1,9 @@
from smileyface import myutil
import os
import sqlite3
import appdirs
import sqlite3
import os
from smileyface import myutil
class DataLayer:
@ -17,10 +18,10 @@ class DataLayer:
return self._db_conn
def _create_db_connection(self):
local_db_filename = self.ctx.config['app']['sqlite_filename']
appdir = appdirs.user_data_dir('smileyface')
local_db_filename = self.ctx.config["app"]["sqlite_filename"]
appdir = appdirs.user_data_dir("smileyface")
fullpath = os.path.join(appdir, local_db_filename)
self.ctx.log['ut4'].info('sqlite3 filename: %s', fullpath)
self.ctx.log["ut4"].info("sqlite3 filename: %s", fullpath)
myutil.ensure_dir_exists(fullpath)
@ -28,9 +29,5 @@ class DataLayer:
return db
def commit(self):
self.ctx.log['db'].info('commit()')
self.ctx.log["db"].info("commit()")
self.db_conn.commit()

@ -1,12 +1,12 @@
from smileyface import myutil
from smileyface import structs
import datetime
import os
import app_skellington._util as apputil
import appdirs
import datetime
import os
import sqlparse
from smileyface import myutil, structs
class DbFuncs:
def __init__(self, ctx, dal):
@ -14,7 +14,7 @@ class DbFuncs:
self.dal = dal
def create_tables(self):
sql_filename = apputil.get_asset(__name__, 'create_schema.sql')
sql_filename = apputil.get_asset(__name__, "create_schema.sql")
with open(sql_filename) as fp:
contents_sql = fp.read()
stmts = sqlparse.split(contents_sql)
@ -22,7 +22,7 @@ class DbFuncs:
conn = self.dal.db_conn
curs = conn.cursor()
for stmt in stmts:
print('----')
print("----")
print(stmt)
curs.execute(stmt)
@ -31,9 +31,9 @@ class DbFuncs:
def truncate_tables(self):
conn = self.dal.db_conn
curs = conn.cursor()
sql = '''
sql = """
truncate file_paks;
'''
"""
for stmt in sqlparse.split(sql):
curs.execute(stmt)
self.dal.commit()
@ -43,7 +43,7 @@ truncate file_paks;
# NOTE(MG) datetime parameter logic could be improved and more complete
conn = self.dal.db_conn
curs = conn.cursor()
sql = '''
sql = """
insert into file_paks (
file_pak_id,
fullpath,
@ -63,12 +63,12 @@ on conflict(filename) do update set
md5sum = excluded.md5sum,
--created at does not update
record_updated_at = datetime('now', 'localtime')
'''
"""
args = (
record.file_pak_id,
record.fullpath,
record.filename,
record.md5sum
record.md5sum,
# record.record_created_at,
# record.record_updated_at
)
@ -80,7 +80,7 @@ on conflict(filename) do update set
# validation data src
conn = self.dal.db_conn
curs = conn.cursor()
sql = '''
sql = """
update file_paks
set
validated_state = ?,
@ -90,7 +90,7 @@ set
where
file_pak_id = ?
'''
"""
args = (validate_state, src, remote_src_md5, rec_id)
curs.execute(sql, args)
conn.commit()
@ -98,14 +98,14 @@ where
def query_filepak(self, filename):
conn = self.dal.db_conn
curs = conn.cursor()
sql = '''
sql = """
select
file_pak_id, fullpath, filename,
md5sum, record_created_at, record_updated_at
from
file_paks
where
lower(filename) = lower(?)'''
lower(filename) = lower(?)"""
args = (filename,)
# print(sql)
curs.execute(sql, args)
@ -126,15 +126,14 @@ where
elif len(output) == 1:
return output[0]
elif len(output) > 1:
input('<breakpoint> unexpected two rows returned from db when expecting to be unique')
input("<breakpoint> unexpected two rows returned from db when expecting to be unique")
return output
return output
def query_invalid_filepaks(self):
conn = self.dal.db_conn
curs = conn.cursor()
sql = '''
sql = """
select
file_pak_id, fullpath, filename,
md5sum,
@ -143,7 +142,7 @@ select
from
file_paks
where
lower(filename) = lower(?)'''
lower(filename) = lower(?)"""
args = (filename,)
# print(sql)
curs.execute(sql, args)
@ -160,7 +159,3 @@ where
filepak.record_updated_at = r[8]
output.append(filepak)
return output

@ -1,6 +1,7 @@
import configparser
import re
class UnrealIniFile:
def __init__(self, filename=None):
self._config = None
@ -14,12 +15,11 @@ class UnrealIniFile:
def filename(self, val):
self._filename = val
if val is not None:
self._config = configparser.RawConfigParser(
strict=False
)
self._config = configparser.RawConfigParser(strict=False)
self._config.optionxform = str # Trick to preserve case in key names
self._config.read(self._filename)
class GameIniSpecial:
def __init__(self, filename):
self._redirect_lines = []
@ -37,24 +37,23 @@ class GameIniSpecial:
def clear_redirect_references(self):
self._redirect_lines = []
def add_redirect_reference(
self, pkg_basename, redirect_url, redirect_protocol,
relative_path, md5sum
):
def add_redirect_reference(self, pkg_basename, redirect_url, redirect_protocol, relative_path, md5sum):
args = {
'pkg_basename': pkg_basename,
'redirect_protocol': redirect_protocol,
'redirect_url': redirect_url,
'relative_path': relative_path,
'md5sum': md5sum
"pkg_basename": pkg_basename,
"redirect_protocol": redirect_protocol,
"redirect_url": redirect_url,
"relative_path": relative_path,
"md5sum": md5sum,
}
########### START multi-line awkward indent
########### START multi-line awkward indent
line = '\
RedirectReferences=(PackageName="{pkg_basename}",\
PackageURLProtocol="{redirect_protocol}",\
PackageURL="{redirect_url}/{relative_path}",\
PackageChecksum="{md5sum}")'.format(**args)
########### END multi-line awkward indent
PackageChecksum="{md5sum}")'.format(
**args
)
########### END multi-line awkward indent
return self.add_redirect_reference_line(line)
@ -65,18 +64,13 @@ PackageChecksum="{md5sum}")'.format(**args)
def write(self, fp):
newcontents = None
with open(self.filename, 'r') as inifile:
with open(self.filename, "r") as inifile:
curcontents = inifile.read()
lines_str = '\n'.join(self._redirect_lines)
lines_str = "\n".join(self._redirect_lines)
newcontents = re.sub(
'RedirectReferences = :PARAM:',
lines_str,
curcontents
)
newcontents = re.sub("RedirectReferences = :PARAM:", lines_str, curcontents)
has_data = True
if has_data:
with open(self.filename, 'w') as fp:
with open(self.filename, "w") as fp:
fp.write(newcontents)

@ -1,10 +1,4 @@
from .gameconfig_edit import UnrealIniFile, GameIniSpecial
from ._util import md5sum_file
from . import myutil
from . import structs
import collections
import configobj
import configparser
import datetime
import glob
@ -14,6 +8,12 @@ import subprocess
import sys
import time
import configobj
from . import myutil, structs
from ._util import md5sum_file
from .gameconfig_edit import GameIniSpecial, UnrealIniFile
class UT4ServerMachine:
def __init__(self, ctx, datalayer):
@ -23,8 +23,6 @@ class UT4ServerMachine:
if not self._validate_env_vars():
sys.exit(1)
def oneclickdeploy(self):
self.generate_instance()
self.upload_redirects()
@ -34,7 +32,7 @@ class UT4ServerMachine:
"""
Deletes the generated instance on the local machine.
"""
self.ctx.log['ut4'].info('Clearing .pak folder...')
self.ctx.log["ut4"].info("Clearing .pak folder...")
cmd = 'rm -rv "$PROJECT_DIR"/instance/LinuxServer/UnrealTournament/Content/Paks/*'
self._invoke_command(cmd)
@ -42,61 +40,46 @@ class UT4ServerMachine:
"""
Create required directories which the user installs maps, mutators, and config to.
"""
dirs = (
'base',
'files/config',
'files/maps',
'files/mutators',
'files/rulesets',
'files/unused'
)
project_dir = self.ctx.config['app']['project_dir']
dirs = ("base", "files/config", "files/maps", "files/mutators", "files/rulesets", "files/unused")
project_dir = self.ctx.config["app"]["project_dir"]
if len(project_dir.strip()) == 0:
project_dir = '.'
print('project_dir:', project_dir)
fullpaths = [
'/'.join([project_dir, d]) for d in dirs
]
project_dir = "."
print("project_dir:", project_dir)
fullpaths = ["/".join([project_dir, d]) for d in dirs]
for fp in fullpaths:
cmd = 'mkdir -p {}'.format(fp)
cmd = "mkdir -p {}".format(fp)
self._invoke_command(cmd)
def download_linux_server(self, x):
"""
Download the latest Linux Unreal Tournament 4 Server from Epic
"""
self.ctx.log['ut4'].info('Downloading Linux Server Binary from Epic.')
self.ctx.log["ut4"].info("Downloading Linux Server Binary from Epic.")
def download_logs(self):
"""
Download the logs from the target hub.
"""
config_dir = self.ctx.config['app']['config_dir']
remote_game_host = self.ctx.config['app']['remote_game_host']
remote_game_dir = self.ctx.config['app']['remote_game_dir']
config_dir = self.ctx.config["app"]["config_dir"]
remote_game_host = self.ctx.config["app"]["remote_game_host"]
remote_game_dir = self.ctx.config["app"]["remote_game_dir"]
self.ctx.log['ut4'].info('Downloading instance logs from target hub.')
cmd = '''
self.ctx.log["ut4"].info("Downloading instance logs from target hub.")
cmd = """
rsync -ravzp {remote_game_host}:{remote_game_dir}/LinuxServer/UnrealTournament/Saved/Logs/ {config_dir}/downloaded-logs/
'''\
.format(**{
'config_dir': config_dir,
'remote_game_host': remote_game_host,
'remote_game_dir': remote_game_dir
})
""".format(
**{"config_dir": config_dir, "remote_game_host": remote_game_host, "remote_game_dir": remote_game_dir}
)
self._invoke_command(cmd)
# Delete logs on remote game server if successfully transferred to local:
self.ctx.log['ut4'].info('')
cmd = '''
self.ctx.log["ut4"].info("")
cmd = """
ssh {remote_game_host} rm {remote_game_dir}/LinuxServer/UnrealTournament/Saved/Logs/* -r'
'''\
.format(**{
'config_dir': config_dir,
'remote_game_host': remote_game_host,
'remote_game_dir': remote_game_dir
})
""".format(
**{"config_dir": config_dir, "remote_game_host": remote_game_host, "remote_game_dir": remote_game_dir}
)
# self._invoke_command(cmd)
def generate_instance(self):
@ -104,34 +87,25 @@ ssh {remote_game_host} rm {remote_game_dir}/LinuxServer/UnrealTournament/Saved/L
Takes the current coniguration and outputs the application files which
can be copied to the server.
"""
self.ctx.log['ut4'].info('Generating server instance from custom files...')
project_dir = self.ctx.config['app']['project_dir']
self.ctx.log["ut4"].info("Generating server instance from custom files...")
project_dir = self.ctx.config["app"]["project_dir"]
# rsync
src = '/'.join([project_dir, 'base/LinuxServer'])
dst = '/'.join([project_dir, 'instance/'])
cmd = 'rsync -ravzp {src} {dst}'.format(**{
'src': src,
'dst': dst
})
src = "/".join([project_dir, "base/LinuxServer"])
dst = "/".join([project_dir, "instance/"])
cmd = "rsync -ravzp {src} {dst}".format(**{"src": src, "dst": dst})
self._invoke_command(cmd)
# cp 1
src = '/'.join([project_dir, 'start-server.sh'])
dst = '/'.join([project_dir, 'instance/'])
cmd = 'cp {src} {dst}'.format(**{
'src': src,
'dst': dst
})
src = "/".join([project_dir, "start-server.sh"])
dst = "/".join([project_dir, "instance/"])
cmd = "cp {src} {dst}".format(**{"src": src, "dst": dst})
self._invoke_command(cmd)
# cp 2
src = '/'.join([project_dir, 'stop-server.sh'])
dst = '/'.join([project_dir, 'instance/'])
cmd = 'cp {src} {dst}'.format(**{
'src': src,
'dst': dst
})
src = "/".join([project_dir, "stop-server.sh"])
dst = "/".join([project_dir, "instance/"])
cmd = "cp {src} {dst}".format(**{"src": src, "dst": dst})
self._invoke_command(cmd)
if self._needs_first_run():
@ -149,35 +123,35 @@ ssh {remote_game_host} rm {remote_game_dir}/LinuxServer/UnrealTournament/Saved/L
"""
Flip on the target hub on for Fragging!
"""
self.ctx.log['ut4'].info('Starting hub...')
self.ctx.log["ut4"].info("Starting hub...")
cmd = '''
cmd = """
ssh {remote_game_host} {remote_game_dir}/start-server.sh
'''
"""
self._invoke_command(cmd)
def stop_server(self):
"""
Stop UT4 Hub processes on the server.
"""
self.ctx.log['ut4'].info('Stopping hub.')
cmd = '''
self.ctx.log["ut4"].info("Stopping hub.")
cmd = """
ssh {remote_game_host} {remote_game_dir}/stop-server.sh
'''
"""
self._invoke_command(cmd)
def upload_redirects(self):
"""
Upload paks to redirect server.
"""
self.ctx.log['ut4'].info('Uploading redirects (maps, mutators, etc.) to target hub.')
self.ctx.log["ut4"].info("Uploading redirects (maps, mutators, etc.) to target hub.")
project_dir = self.ctx.config['app']['project_dir']
project_dir = self.ctx.config["app"]["project_dir"]
# paks_dir = os.path.join(project_dir, 'instance/LinuxServer/UnrealTournament/Content/Paks/')
paks_dir = os.path.join(project_dir, 'files/') # trailing slash required
remote_redirect_host = self.ctx.config['app']['remote_redirect_host']
paks_dir = os.path.join(project_dir, "files/") # trailing slash required
remote_redirect_host = self.ctx.config["app"]["remote_redirect_host"]
cwd = project_dir
cmd = '''
cmd = """
rsync -rivz \
--delete \
--exclude "*.md5" \
@ -186,11 +160,12 @@ rsync -rivz \
--exclude Mods.db \
{paks_dir} {remote_redirect_host}
'''\
.format(**{
'paks_dir': paks_dir,
'remote_redirect_host': remote_redirect_host,
})
""".format(
**{
"paks_dir": paks_dir,
"remote_redirect_host": remote_redirect_host,
}
)
# subprocess.run(cmd, cwd=cwd) # should be invoke_command? no because gui will need subprocess.run
self._invoke_command(cmd)
@ -199,61 +174,66 @@ rsync -rivz \
self._redirect_chown()
def _redirect_hide_passwords(self):
project_dir = self.ctx.config['app']['project_dir']
remote_redirect_host = self.ctx.config['app']['remote_redirect_host']
project_dir = self.ctx.config["app"]["project_dir"]
remote_redirect_host = self.ctx.config["app"]["remote_redirect_host"]
# (on the server):
gameini="/srv/ut4-redirect.zavage.net/config/Game.ini"
engineini="/srv/ut4-redirect.zavage.net/config/Engine.ini"
gameini = "/srv/ut4-redirect.zavage.net/config/Game.ini"
engineini = "/srv/ut4-redirect.zavage.net/config/Engine.ini"
cmd = '''
cmd = """
ssh mathewguest.com \
sed -i /ServerInstanceID=/c\ServerInstanceID=Hidden {gameini}
'''.format(gameini=gameini)
""".format(
gameini=gameini
)
self._invoke_command(cmd)
cmd = '''
cmd = """
ssh mathewguest.com \
sed -i /RconPassword=/c\RconPassword=Hidden {engineini}
'''.format(engineini=engineini)
""".format(
engineini=engineini
)
self._invoke_command(cmd)
def _redirect_upload_script(self):
project_dir = self.ctx.config['app']['project_dir']
remote_redirect_host = self.ctx.config['app']['remote_redirect_host']
cmd = '''
project_dir = self.ctx.config["app"]["project_dir"]
remote_redirect_host = self.ctx.config["app"]["remote_redirect_host"]
cmd = """
rsync -vz \
{project_dir}/ut4-server-ctl.sh \
{remote_redirect_host}
'''\
.format(**{
'project_dir': project_dir,
'remote_redirect_host': remote_redirect_host,
})
""".format(
**{
"project_dir": project_dir,
"remote_redirect_host": remote_redirect_host,
}
)
# subprocess.run(cmd, cwd=cwd) # should be invoke_command? no because gui will need subprocess.run
self._invoke_command(cmd)
def _redirect_chown(self):
project_dir = self.ctx.config['app']['project_dir']
remote_redirect_host = self.ctx.config['app']['remote_redirect_host']
project_dir = self.ctx.config["app"]["project_dir"]
remote_redirect_host = self.ctx.config["app"]["remote_redirect_host"]
cmd = '''
cmd = """
ssh mathewguest.com \
chown http:http /srv/ut4-redirect.zavage.net -R
'''
"""
self._invoke_command(cmd)
def upload_server(self):
"""
Upload all required game files to the hub server.
"""
self.ctx.log['ut4'].info('Uploading customized server')
project_dir = self.ctx.config['app']['project_dir']
remote_game_host = self.ctx.config['app']['remote_game_host']
remote_game_dir = self.ctx.config['app']['remote_game_dir']
self.ctx.log["ut4"].info("Uploading customized server")
project_dir = self.ctx.config["app"]["project_dir"]
remote_game_host = self.ctx.config["app"]["remote_game_host"]
remote_game_dir = self.ctx.config["app"]["remote_game_dir"]
cwd = None
# transfer #1
cmd = '''
cmd = """
rsync -raivzp \
--delete \
--exclude ".KEEP" \
@ -266,88 +246,72 @@ rsync -raivzp \
--exclude "Saved/Logs/*" \
{project_dir}/instance/ \
{remote_game_host}:{remote_game_dir}
'''\
.format(**{
'project_dir': project_dir,
'remote_game_host': remote_game_host,
'remote_game_dir': remote_game_dir
})
cmd = cmd.replace(' ', '')
""".format(
**{"project_dir": project_dir, "remote_game_host": remote_game_host, "remote_game_dir": remote_game_dir}
)
cmd = cmd.replace(" ", "")
# subprocess.run(cmd, cwd=cwd) # should be invoke_command? no because gui will need subprocess.run
self._invoke_command(cmd)
# transfer #2
cmd = '''
cmd = """
rsync -avzp \
{project_dir}/ut4-server-ctl.sh \
{remote_game_host}:{remote_game_dir}
'''\
.format(**{
'project_dir': project_dir,
'remote_game_host': remote_game_host,
'remote_game_dir': remote_game_dir
})
""".format(
**{"project_dir": project_dir, "remote_game_host": remote_game_host, "remote_game_dir": remote_game_dir}
)
# subprocess.run(cmd, cwd=cwd)
self._invoke_command(cmd)
# transfer #3
cmd = '''
cmd = """
scp \
{project_dir}/instance/ut4-server.service \
{remote_game_host}:/etc/systemd/system/
'''\
.format(**{
'project_dir': project_dir,
'remote_game_host': remote_game_host,
'remote_game_dir': remote_game_dir
})
""".format(
**{"project_dir": project_dir, "remote_game_host": remote_game_host, "remote_game_dir": remote_game_dir}
)
# subprocess.run(cmd, cwd=cwd)
self._invoke_command(cmd)
# transfer #4
cmd = '''
cmd = """
ssh {remote_game_host} \
chown ut4:ut4 {remote_game_dir} -R
'''.format(**{
'remote_game_host': remote_game_host,
'remote_game_dir': remote_game_dir
})
""".format(
**{"remote_game_host": remote_game_host, "remote_game_dir": remote_game_dir}
)
# subprocess.run(cmd, cwd=cwd)
self._invoke_command(cmd)
# Fix +x permissions on bash scripts
cmd = '''
cmd = """
ssh {remote_game_host} \
chmod +x \
{remote_game_dir}/start-server.sh \
{remote_game_dir}/stop-server.sh
'''.format(**{
'remote_game_host': remote_game_host,
'remote_game_dir': remote_game_dir
})
""".format(
**{"remote_game_host": remote_game_host, "remote_game_dir": remote_game_dir}
)
# subprocess.run(cmd, cwd=cwd)
self._invoke_command(cmd)
def _first_run(self):
self.ctx.log['ut4'].info('Starting instance once to get UID.')
self.ctx.log['ut4'].info('Unfortunately, this takes 20 seconds. Just wait.')
self.ctx.log["ut4"].info("Starting instance once to get UID.")
self.ctx.log["ut4"].info("Unfortunately, this takes 20 seconds. Just wait.")
# Make binary executable:
bin_name = 'UE4Server-Linux-Shipping'
project_dir = self.ctx.config['app']['project_dir']
bin_name = "UE4Server-Linux-Shipping"
project_dir = self.ctx.config["app"]["project_dir"]
cwd = '{project_dir}/instance/LinuxServer/Engine/Binaries/Linux'\
.format(project_dir=project_dir)
target_file = '{cwd}/{bin_name}'.format(cwd=cwd, bin_name=bin_name)
cmd = ['chmod', '770', target_file]
cwd = "{project_dir}/instance/LinuxServer/Engine/Binaries/Linux".format(project_dir=project_dir)
target_file = "{cwd}/{bin_name}".format(cwd=cwd, bin_name=bin_name)
cmd = ["chmod", "770", target_file]
p = subprocess.run(cmd)
cmd = ['./'+bin_name, 'UnrealTournament', 'UT-Entry?Game=Lobby', '-log']
cmd = ["./" + bin_name, "UnrealTournament", "UT-Entry?Game=Lobby", "-log"]
p = subprocess.Popen(cmd, cwd=cwd, stdout=subprocess.PIPE)
try:
@ -356,68 +320,68 @@ ssh {remote_game_host} \
p.kill()
stdout, stderr = p.communicate()
self.ctx.log['ut4'].info('sleeping 20 seconds and then we\'ll kill the server we started just now.')
self.ctx.log["ut4"].info("sleeping 20 seconds and then we'll kill the server we started just now.")
# # TODO(MG) get uid and export
def _install_config(self):
files = (
'Game.ini',
'Engine.ini'
)
project_dir = self.ctx.config['app']['project_dir']
config_dir = self.ctx.config['app']['config_dir']
files = ("Game.ini", "Engine.ini")
project_dir = self.ctx.config["app"]["project_dir"]
config_dir = self.ctx.config["app"]["config_dir"]
for fn in files:
self.ctx.log['ut4'].info('Installing file: %s', fn)
self.ctx.log["ut4"].info("Installing file: %s", fn)
src = os.path.join(config_dir, fn)
dst = os.path.join(project_dir, 'instance/LinuxServer/UnrealTournament/Saved/Config/LinuxServer', fn)
cmd = 'cp {src} {dst}'.format(**{
'src': src,
'dst': dst
})
dst = os.path.join(project_dir, "instance/LinuxServer/UnrealTournament/Saved/Config/LinuxServer", fn)
cmd = "cp {src} {dst}".format(**{"src": src, "dst": dst})
self._invoke_command(cmd)
# Monkey-patch Game.ini to ensure it has a place for RedirectReferences
if fn == 'Game.ini':
if fn == "Game.ini":
ini = UnrealIniFile(dst)
sect_name = '/Script/UnrealTournament.UTBaseGameMode'
opt_name = 'RedirectReferences'
sect_name = "/Script/UnrealTournament.UTBaseGameMode"
opt_name = "RedirectReferences"
if not ini._config.has_section(sect_name):
ini._config.add_section(sect_name)
if not ini._config.has_option(sect_name, opt_name):
ini._config.set(sect_name, opt_name, ':PARAM:')
with open(dst, 'w') as fp:
ini._config.set(sect_name, opt_name, ":PARAM:")
with open(dst, "w") as fp:
ini._config.write(fp)
def _install_paks(self):
project_dir = self.ctx.config['app']['project_dir']
project_dir = self.ctx.config["app"]["project_dir"]
self.ctx.log['ut4'].info('Installing maps...')
cmd = 'rsync -ravzp {src} {dst}'.format(**{
'src': '/'.join([project_dir, 'files/maps/']),
'dst': '/'.join([project_dir, 'instance/LinuxServer/UnrealTournament/Content/Paks/'])
})
self.ctx.log["ut4"].info("Installing maps...")
cmd = "rsync -ravzp {src} {dst}".format(
**{
"src": "/".join([project_dir, "files/maps/"]),
"dst": "/".join([project_dir, "instance/LinuxServer/UnrealTournament/Content/Paks/"]),
}
)
self._invoke_command(cmd)
self.ctx.log['ut4'].info('Installing mutators...')
cmd = 'rsync -ravzp {src} {dst}'.format(**{
'src': '/'.join([project_dir, 'files/mutators/']),
'dst': '/'.join([project_dir, 'instance/LinuxServer/UnrealTournament/Content/Paks/'])
})
self.ctx.log["ut4"].info("Installing mutators...")
cmd = "rsync -ravzp {src} {dst}".format(
**{
"src": "/".join([project_dir, "files/mutators/"]),
"dst": "/".join([project_dir, "instance/LinuxServer/UnrealTournament/Content/Paks/"]),
}
)
self._invoke_command(cmd)
def _install_redirect_lines(self):
self.ctx.log['ut4'].info('Generating redirect references...')
self.ctx.log["ut4"].info("Generating redirect references...")
redirect_protocol = self.ctx.config['app']['redirect_protocol']
redirect_url = self.ctx.config['app']['redirect_url']
project_dir = self.ctx.config['app']['project_dir']
mod_dir = '/'.join([project_dir, 'files'])
redirect_protocol = self.ctx.config["app"]["redirect_protocol"]
redirect_url = self.ctx.config["app"]["redirect_url"]
project_dir = self.ctx.config["app"]["project_dir"]
mod_dir = "/".join([project_dir, "files"])
game_ini_filepath = '/'.join([project_dir, 'instance/LinuxServer/UnrealTournament/Saved/Config/LinuxServer/Game.ini'])
game_ini_filepath = "/".join(
[project_dir, "instance/LinuxServer/UnrealTournament/Saved/Config/LinuxServer/Game.ini"]
)
game_ini = GameIniSpecial(game_ini_filepath)
game_ini.clear_redirect_references()
files = glob.glob('{}/**/*.pak'.format(mod_dir))
files = glob.glob("{}/**/*.pak".format(mod_dir))
redirect_lines = []
for idx, filename in enumerate(files):
# if idx > 5:
@ -435,57 +399,52 @@ ssh {remote_game_host} \
print(ex)
continue
line = game_ini.add_redirect_reference(**{
'pkg_basename': pkg_basename,
'redirect_protocol': redirect_protocol,
'redirect_url': redirect_url,
'relative_path': relative_path,
'md5sum': md5sum
})
line = game_ini.add_redirect_reference(
**{
"pkg_basename": pkg_basename,
"redirect_protocol": redirect_protocol,
"redirect_url": redirect_url,
"relative_path": relative_path,
"md5sum": md5sum,
}
)
self.ctx.log['ut4'].debug("redirect line = '%s'", line)
self.ctx.log["ut4"].debug("redirect line = '%s'", line)
data = game_ini.write(sys.stdout)
def _install_rulesets(self):
self.ctx.log['ut4'].info('Concatenating rulesets for game modes...')
project_dir = self.ctx.config['app']['project_dir']
self.ctx.log["ut4"].info("Concatenating rulesets for game modes...")
project_dir = self.ctx.config["app"]["project_dir"]
src_dir = '/'.join([project_dir, 'files/rulesets'])
out_dir = '/'.join([project_dir, '/instance/LinuxServer/UnrealTournament/Saved/Config/Rulesets'])
out_filename='/'.join([out_dir, 'ruleset.json'])
src_dir = "/".join([project_dir, "files/rulesets"])
out_dir = "/".join([project_dir, "/instance/LinuxServer/UnrealTournament/Saved/Config/Rulesets"])
out_filename = "/".join([out_dir, "ruleset.json"])
cmd = 'mkdir -pv {out_dir}'.format(**{
'out_dir': out_dir
})
cmd = "mkdir -pv {out_dir}".format(**{"out_dir": out_dir})
self._invoke_command(cmd)
self.ctx.log['ut4'].info('out filename=%s', out_filename)
self.ctx.log["ut4"].info("out filename=%s", out_filename)
# echo {\"rules\":[ > "$OUT_FILENAME"
cmd = "echo '{{\"rules\":[' > \"{out_filename}\"".format(out_filename=out_filename)
cmd = 'echo \'{{"rules":[\' > "{out_filename}"'.format(out_filename=out_filename)
self._invoke_command(cmd)
cmd = 'for f in "{src_dir}"/*.json ; do cat "$f" >> "{out_filename}" ; done'.format(
**{
'src_dir': src_dir,
'out_filename': out_filename
})
**{"src_dir": src_dir, "out_filename": out_filename}
)
self._invoke_command(cmd)
cmd = 'echo "]}}" >> "{out_filename}"'.format(
**{
'out_filename': out_filename
})
cmd = 'echo "]}}" >> "{out_filename}"'.format(**{"out_filename": out_filename})
self._invoke_command(cmd)
self.ctx.log['ut4'].info('output ruleset is at "%s"', out_filename)
self.ctx.log["ut4"].info('output ruleset is at "%s"', out_filename)
def _invoke_command(self, cmd, msg=None):
assert isinstance(cmd, str), 'cmd input must be string: %s'.format(cmd)
assert isinstance(cmd, str), "cmd input must be string: %s".format(cmd)
if msg is None:
msg = cmd
print(msg)
self.ctx.log['ut4'].info('running cmd: %s', cmd)
self.ctx.log["ut4"].info("running cmd: %s", cmd)
cwd = None # todo(mg) ?
os.system(cmd)
@ -494,31 +453,31 @@ ssh {remote_game_host} \
def _validate_env_vars(self):
variable_names = (
'project_dir',
'download_url',
'download_filename',
'download_md5',
'redirect_protocol',
'redirect_url',
'remote_game_host',
'remote_game_dir',
'remote_redirect_host'
"project_dir",
"download_url",
"download_filename",
"download_md5",
"redirect_protocol",
"redirect_url",
"remote_game_host",
"remote_game_dir",
"remote_redirect_host",
)
for name in variable_names:
value = self.ctx.config['app'][name]
self.ctx.log['ut4'].info('%s: %s', name, value)
value = self.ctx.config["app"][name]
self.ctx.log["ut4"].info("%s: %s", name, value)
i = input('Continue with above configuration? (y/N):')
if i.lower() != 'y':
self.ctx.log['ut4'].info('Doing nothing.')
i = input("Continue with above configuration? (y/N):")
if i.lower() != "y":
self.ctx.log["ut4"].info("Doing nothing.")
return False
self.ctx.log['ut4'].info('Continuing.')
self.ctx.log["ut4"].info("Continuing.")
return True
class MultiOrderedDict(collections.OrderedDict):
def __setitem__(self, key, value):
if isinstance(value, list) and key in self:
self[key].extend(value)
else:
super().__setitem__(key, value)

@ -5,14 +5,14 @@ import os
def ensure_dir_exists(dirpath):
if dirpath is None:
return
if dirpath == '':
if dirpath == "":
return
dirpath = os.path.dirname(dirpath)
os.makedirs(dirpath, exist_ok=True)
def md5_file(filename):
with open(filename, 'rb') as fp:
with open(filename, "rb") as fp:
data = fp.read()
h = hashlib.md5(data).hexdigest()
return h

@ -1,3 +1,3 @@
from .local_fs import *
from .scrape_utcc import *
from .scrape_ut4pugs import *
from .scrape_utcc import *

@ -1,9 +1,8 @@
from smileyface import myutil
from smileyface import structs
import datetime
import os
from smileyface import myutil, structs
class LocalFs:
def __init__(self, ctx, datalayer):
@ -14,12 +13,12 @@ class LocalFs:
self.datalayer.create_tables()
def load_md5s(self):
paks_dir = self.ctx.config['app']['project_dir']
maps_dir = os.path.join(paks_dir, 'files', 'maps')
paks_dir = self.ctx.config["app"]["project_dir"]
maps_dir = os.path.join(paks_dir, "files", "maps")
print(maps_dir)
self._load_md5_one_dir(maps_dir)
muts_dir = os.path.join(paks_dir, 'files', 'mutators')
muts_dir = os.path.join(paks_dir, "files", "mutators")
print(muts_dir)
self._load_md5_one_dir(muts_dir)

@ -1,8 +1,8 @@
import selenium
import selenium.webdriver
URL_MUTATORS = 'https://ut4pugs.us/redirect-mutators'
URL_MAPS = 'https://ut4pugs.us/redirect-mutators'
URL_MUTATORS = "https://ut4pugs.us/redirect-mutators"
URL_MAPS = "https://ut4pugs.us/redirect-mutators"
class ScrapeUt4Pugs:
@ -25,14 +25,14 @@ class ScrapeUt4Pugs:
self._check_pak_md5sums()
def _check_pak_md5sums(self):
tbl_of_mutators = self.browser.find_element_by_id('myTable')
tbl_of_mutators = self.browser.find_element_by_id("myTable")
print(tbl_of_mutators)
mut_rows = tbl_of_mutators.find_elements_by_xpath('tbody/tr')
mut_rows = tbl_of_mutators.find_elements_by_xpath("tbody/tr")
for r in mut_rows:
mut_cols = r.find_elements_by_xpath('td')
mut_cols = r.find_elements_by_xpath("td")
if len(mut_cols) != 3:
input('<breakpoint> at unexpected columns for mutator. received {}'.format(len(mut_cols)))
input("<breakpoint> at unexpected columns for mutator. received {}".format(len(mut_cols)))
mut_file = mut_cols[0]
mut_md5 = mut_cols[1]
mut_ini_line = mut_cols[2]
@ -44,22 +44,22 @@ class ScrapeUt4Pugs:
local_file = self.datalayer.query_filepak(mut_file.text)
print(local_file)
if not local_file:
self.ctx.log['ut4'].warn('pak not found locally: %s', mut_file.text)
self.ctx.log["ut4"].warn("pak not found locally: %s", mut_file.text)
continue
local_md5 = local_file.md5sum
remote_md5 = mut_md5.text
if local_md5 != remote_md5:
input('<breakpoint> as mismatching md5!')
print('local: ', local_md5)
print('remote: ', remote_md5)
self.datalayer.mark_filepak_validated_state(local_file.file_pak_id, 'mismatch', 'ut4pugs', remote_md5)
input("<breakpoint> as mismatching md5!")
print("local: ", local_md5)
print("remote: ", remote_md5)
self.datalayer.mark_filepak_validated_state(local_file.file_pak_id, "mismatch", "ut4pugs", remote_md5)
else:
input('<breakpoint> as matching md5! good job')
self.datalayer.mark_filepak_validated_state(local_file.file_pak_id, 'valid', 'ut4pugs', None)
input("<breakpoint> as matching md5! good job")
self.datalayer.mark_filepak_validated_state(local_file.file_pak_id, "valid", "ut4pugs", None)
# print(r)
print('xxx')
print("xxx")
pass
pass

@ -1,7 +1,7 @@
import selenium
import selenium.webdriver
URL_CARDS_LIST = 'https://utcc.unrealpugs.com/content'
URL_CARDS_LIST = "https://utcc.unrealpugs.com/content"
class ScrapeUtcc:
@ -21,4 +21,3 @@ class ScrapeUtcc:
def _scrape_list_of_content_cards(self):
self.browser.get(URL_CARDS_LIST)