Add Factorio/mod_updater.py
From Github: https://github.com/pdemonaco/factorio-mod-updater Check there for updates
This commit is contained in:
parent
76f4f05807
commit
d7416f87d3
|
@ -0,0 +1,733 @@
|
|||
#!/usr/bin/python3
|
||||
"""
|
||||
This module provides a simple method to manage updating and installing mods
|
||||
on a given factorio server.
|
||||
|
||||
It is currently not intended to be imported and instead should be executed
|
||||
directly as a python script.
|
||||
"""
|
||||
import argparse
|
||||
from collections import OrderedDict
|
||||
from datetime import datetime
|
||||
from enum import Enum, auto
|
||||
import glob
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# External URL processing library
|
||||
# http://docs.python-requests.org/en/master/user/quickstart/
|
||||
import requests
|
||||
|
||||
|
||||
def _validate_hash(checksum: str, target: str, bsize: int = 65536) -> bool:
|
||||
"""
|
||||
Checks to see if the file specified by target matches the provided sha1
|
||||
checksum.
|
||||
|
||||
Keyword Arguments:
|
||||
checksum -- sha1 digest to be matched
|
||||
target -- path to the file which must be validated
|
||||
"""
|
||||
hasher = hashlib.sha1()
|
||||
|
||||
with open(target, "rb") as target_fp:
|
||||
block = target_fp.read(bsize)
|
||||
while len(block) > 0:
|
||||
hasher.update(block)
|
||||
block = target_fp.read(bsize)
|
||||
|
||||
return hasher.hexdigest() == checksum
|
||||
|
||||
|
||||
def _version_match(installed: str, mod: str):
|
||||
"""Checks if factorio versions are compatible."""
|
||||
if installed.startswith("1.") and mod == "0.18":
|
||||
return True
|
||||
return installed == mod
|
||||
|
||||
|
||||
class ModUpdater:
|
||||
"""
|
||||
Internal class managing the current version and state of the mods on this
|
||||
server.
|
||||
"""
|
||||
|
||||
MOD_VERSION_PATTERN = r"\d+[.]\d+[.]\d+"
|
||||
MOD_FILE_PATTERN = "^(.*)_({version})[.]zip$".format(version=MOD_VERSION_PATTERN)
|
||||
|
||||
class Mode(Enum):
|
||||
"""Possible execution modes"""
|
||||
|
||||
LIST = auto()
|
||||
UPDATE = auto()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
settings_path: str,
|
||||
data_path: str,
|
||||
mod_path: str,
|
||||
fact_path: str,
|
||||
creds: hash,
|
||||
title_mode: bool,
|
||||
):
|
||||
"""
|
||||
Initialize the updater class with all mandatory and optional arguments.
|
||||
|
||||
Keyword arguments:
|
||||
settings_path -- absolute path to the server-settings.json file
|
||||
mod_path -- absolute path to the factorio mod directory
|
||||
fact_ver -- local factorio version
|
||||
"""
|
||||
self.mod_server_url = "https://mods.factorio.com"
|
||||
self.mod_path = mod_path
|
||||
self.timestamp = datetime.utcnow()
|
||||
self.title_mode = title_mode
|
||||
|
||||
# Get the credentials to download mods
|
||||
if settings_path is not None:
|
||||
self.settings = self._parse_settings(settings_path)
|
||||
else:
|
||||
self.settings = {}
|
||||
if data_path is not None:
|
||||
self.data = self._parse_settings(data_path)
|
||||
else:
|
||||
self.data = {}
|
||||
|
||||
# Parse username and token
|
||||
if "username" in creds and creds["username"] is not None:
|
||||
self.username = creds["username"]
|
||||
elif "username" in self.settings:
|
||||
self.username = self.settings["username"]
|
||||
elif "service-username" in self.data:
|
||||
self.username = self.data["service-username"]
|
||||
else:
|
||||
self.token = None
|
||||
|
||||
if "token" in creds and creds["token"] is not None:
|
||||
self.token = creds["token"]
|
||||
elif "token" in self.settings:
|
||||
self.token = self.settings["token"]
|
||||
elif "service-token" in self.data:
|
||||
self.token = self.data["service-token"]
|
||||
else:
|
||||
self.token = None
|
||||
|
||||
# Ensure username and token were specified
|
||||
if self.username is None or self.username == "":
|
||||
errmsg = (
|
||||
"error: username not specified in "
|
||||
+ "server-settings.json, player-data.json, or cli!"
|
||||
)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if self.token is None or self.token == "":
|
||||
errmsg = (
|
||||
"error: token not specified in "
|
||||
+ "server-settings.json, player-data.json, or cli!"
|
||||
)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Begin processing
|
||||
self._determine_version(fact_path)
|
||||
self._parse_mod_list()
|
||||
self._retrieve_metadata()
|
||||
self._determine_max_name_lengths()
|
||||
if self.title_mode:
|
||||
self.mods = OrderedDict(
|
||||
sorted(self.mods.items(), key=lambda mod: mod[1]["title"])
|
||||
)
|
||||
else:
|
||||
self.mods = OrderedDict(sorted(self.mods.items()))
|
||||
|
||||
def _determine_version(self, fact_path: str):
|
||||
"""Determine the local factorio version"""
|
||||
if not os.path.exists(fact_path):
|
||||
errmsg = "error: factorio binary '{fpath_path}' does not exist!"
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
output = subprocess.check_output(
|
||||
[fact_path, "--version"], universal_newlines=True
|
||||
)
|
||||
ver_re = re.compile(r"Version: (\d+)[.](\d+)[.](\d+) .*\n", re.RegexFlag.M)
|
||||
match = ver_re.match(output)
|
||||
if match:
|
||||
version = {}
|
||||
version["major"] = match.group(1)
|
||||
version["minor"] = match.group(2)
|
||||
version["patch"] = match.group(3)
|
||||
version["release"] = "{}.{}".format(version["major"], version["minor"])
|
||||
self.fact_version = version
|
||||
else:
|
||||
errmsg = "Unable to parse version from:\n{output}".format(output=output)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit("1")
|
||||
|
||||
except subprocess.CalledProcessError as error:
|
||||
errmsg = ("error: failed to run '{fpath} --version': " "{errstr}").format(
|
||||
fpath=fact_path, errstr=error.stderr
|
||||
)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
print(
|
||||
"Factorio Release: {release}\n".format(release=self.fact_version["release"])
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _parse_settings(config_path: str):
|
||||
"""Process the specified server-settings.json or player-data.json file."""
|
||||
try:
|
||||
with open(config_path, "r") as config_fp:
|
||||
return json.load(config_fp)
|
||||
except IOError as error:
|
||||
errmsg = ("error: failed to open file '{fname}': " "{errstr}").format(
|
||||
fname=config_path, errstr=error.strerror
|
||||
)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError as error:
|
||||
errmsg = ("error: failed to parse json file '{fname}': " "{errstr}").format(
|
||||
fname=config_path, errstr=error.msg
|
||||
)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def _retrieve_metadata(self):
|
||||
"""
|
||||
Pull the latest metadata for each mod from the factorio server
|
||||
See https://wiki.factorio.com/Mod_portal_API for details
|
||||
"""
|
||||
print("Retrieving metadata", end="")
|
||||
for mod, data in self.mods.items():
|
||||
self._retrieve_mod_metadata(mod)
|
||||
print(".", end="", flush=True)
|
||||
print("complete!\n")
|
||||
|
||||
# Add missing dependencies to the overall list
|
||||
while True:
|
||||
missing_mods = []
|
||||
for mod, data in self.mods.items():
|
||||
if "missing_deps" in data:
|
||||
missing_mods.extend(data["missing_deps"])
|
||||
|
||||
unique_missing = set(missing_mods)
|
||||
for mod in self.mods.keys():
|
||||
if mod in unique_missing:
|
||||
unique_missing.remove(mod)
|
||||
if len(unique_missing) == 0:
|
||||
break
|
||||
for mod in unique_missing:
|
||||
entry = {}
|
||||
entry["enabled"] = True
|
||||
entry["installed"] = False
|
||||
self.mods[mod] = entry
|
||||
self._retrieve_mod_metadata(mod)
|
||||
print("Info: adding missing dependency {dep}".format(dep=mod))
|
||||
|
||||
for mod, data in self.mods.items():
|
||||
if "metadata" not in data:
|
||||
warnmsg = (
|
||||
"Warning: Unable to retrieve metadata for"
|
||||
" {mod}, skipped!".format(mod=mod)
|
||||
)
|
||||
print(warnmsg)
|
||||
|
||||
def _retrieve_mod_metadata(self, mod: str):
|
||||
"""
|
||||
Attempts to retrieve the metadata for the target mod. If found, the
|
||||
data object is updated with the 'metadata' key and the 'latest' keys.
|
||||
"""
|
||||
data = self.mods[mod]
|
||||
mod_url = self.mod_server_url + "/api/mods/" + mod + "/full"
|
||||
with requests.get(mod_url) as req:
|
||||
if req.status_code == 200:
|
||||
data["metadata"] = req.json()
|
||||
|
||||
if "metadata" in data:
|
||||
# Find the latest release for this version of Factorio
|
||||
matching_releases = []
|
||||
for rel in data["metadata"]["releases"]:
|
||||
rel_ver = rel["info_json"]["factorio_version"]
|
||||
if _version_match(installed=self.fact_version["release"], mod=rel_ver):
|
||||
matching_releases.append(rel)
|
||||
|
||||
if len(matching_releases) > 0:
|
||||
data["latest"] = matching_releases[-1]
|
||||
|
||||
# Add title key
|
||||
data["title"] = data["metadata"]["title"]
|
||||
|
||||
# Mark whether it's deprecated
|
||||
data["deprecated"] = data["metadata"].get("deprecated", False)
|
||||
else:
|
||||
data["title"] = mod
|
||||
|
||||
# Assume not deprecated if we can't find it
|
||||
data["deprecated"] = False
|
||||
|
||||
if "latest" in data:
|
||||
self._resolve_dependencies(mod)
|
||||
|
||||
def _resolve_dependencies(self, mod: str):
|
||||
"""
|
||||
Processes the dependency list for this mod and returns an array
|
||||
listing those which are not currently enabled. Note that this skips
|
||||
exclusions and optional dependencies. (! and ?)
|
||||
"""
|
||||
data = self.mods[mod]
|
||||
if "latest" in data:
|
||||
data["missing_deps"] = []
|
||||
data["dependencies"] = {}
|
||||
dependencies = data["latest"]["info_json"]["dependencies"]
|
||||
# Preparation for future explicit version matching
|
||||
dep_pattern = re.compile(r"^([\w -]+) ([<=>][=])? (\d+[.]\d+[.]\d+)$")
|
||||
for dep_entry in dependencies:
|
||||
match = dep_pattern.fullmatch(dep_entry)
|
||||
if match:
|
||||
dep = {}
|
||||
dep_name = match.group(1)
|
||||
if dep_name == "base":
|
||||
continue
|
||||
dep["argument"] = match.group(2)
|
||||
dep["version"] = match.group(3)
|
||||
data["dependencies"][match.group(1)] = dep
|
||||
|
||||
for dep_name in data["dependencies"].keys():
|
||||
if dep_name not in self.mods:
|
||||
data["missing_deps"].append(dep_name)
|
||||
|
||||
def _parse_mod_list(self):
|
||||
"""Process the mod-list.json within mod_path."""
|
||||
mod_list_path = os.path.join(self.mod_path, "mod-list.json")
|
||||
try:
|
||||
settings_fp = open(mod_list_path, "r")
|
||||
mod_json = json.load(settings_fp)
|
||||
self.mods = {}
|
||||
if "mods" in mod_json:
|
||||
for mod in mod_json["mods"]:
|
||||
entry = {}
|
||||
entry["enabled"] = mod["enabled"]
|
||||
self.mods[mod["name"]] = entry
|
||||
else:
|
||||
print(
|
||||
"Invalid mod-list.json file \
|
||||
'{path}'!".format(
|
||||
path=mod_list_path
|
||||
),
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Remove the 'base' mod as it's not relevant to this process
|
||||
if "base" in self.mods:
|
||||
del self.mods["base"]
|
||||
except IOError as error:
|
||||
errmsg = ("error: failed to open file '{fname}': " "{errstr}").format(
|
||||
fname=mod_list_path, errstr=error.strerror
|
||||
)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError as error:
|
||||
errmsg = ("error: failed to parse json file '{fname}': " "{errstr}").format(
|
||||
fname=mod_list_path, errstr=error.msg
|
||||
)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Collect the installed state & versions
|
||||
self.mod_files = glob.glob("{mod_path}/*.zip".format(mod_path=self.mod_path))
|
||||
installed_mods = {}
|
||||
mod_pattern = re.compile(self.MOD_FILE_PATTERN)
|
||||
for entry in self.mod_files:
|
||||
basename = os.path.basename(entry)
|
||||
match = mod_pattern.fullmatch(basename)
|
||||
if match:
|
||||
installed_mods[match.group(1)] = match.group(2)
|
||||
|
||||
for mod, data in self.mods.items():
|
||||
if mod in installed_mods:
|
||||
data["installed"] = True
|
||||
data["version"] = installed_mods[mod]
|
||||
else:
|
||||
data["installed"] = False
|
||||
|
||||
def _update_mod_list(self):
|
||||
"""
|
||||
Generates an updated mod-list.json file which takes into account any
|
||||
newly added dependencies.
|
||||
"""
|
||||
# Build the simplified object for json output
|
||||
mod_list_output = {}
|
||||
mod_list_output["mods"] = []
|
||||
for mod, data in self.mods.items():
|
||||
mod_entry = {}
|
||||
mod_entry["name"] = mod
|
||||
mod_entry["enabled"] = data["enabled"]
|
||||
mod_list_output["mods"].append(mod_entry)
|
||||
|
||||
# Rename the old mod-list file with a timestamp
|
||||
mod_list_path = os.path.join(self.mod_path, "mod-list.json")
|
||||
mod_list_backup_path = os.path.join(
|
||||
self.mod_path,
|
||||
"mod-list.{timestamp}.json".format(
|
||||
timestamp=self.timestamp.strftime("%Y-%m-%d_%H%M.%S")
|
||||
),
|
||||
)
|
||||
try:
|
||||
os.rename(src=mod_list_path, dst=mod_list_backup_path)
|
||||
except IOError as error:
|
||||
errmsg = (
|
||||
"error: failed to rename file '{s}' to '{d}': " "{errstr}"
|
||||
).format(s=mod_list_path, d=mod_list_backup_path, errstr=error.strerror)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Store the current mod list
|
||||
try:
|
||||
mod_list_fp = open(mod_list_path, "w")
|
||||
mod_list_fp.write(json.dumps(mod_list_output, indent=2, sort_keys=True))
|
||||
except IOError as error:
|
||||
errmsg = (
|
||||
"error: failed to store updated mod list file '{s}': " "{errstr}"
|
||||
).format(s=mod_list_path, errstr=error.strerror)
|
||||
print(errmsg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def _determine_max_name_lengths(self):
|
||||
"""Returns the length of the longest mod name"""
|
||||
max_mod_len = 0
|
||||
max_cver_len = 0
|
||||
max_lver_len = 0
|
||||
for mod, data in self.mods.items():
|
||||
mod_len = len(data["title"]) if self.title_mode else len(mod)
|
||||
max_mod_len = mod_len if mod_len > max_mod_len else max_mod_len
|
||||
cver_len = len(data["version"]) if data["installed"] else len("Version")
|
||||
max_cver_len = cver_len if cver_len > max_cver_len else max_cver_len
|
||||
lver_len = (
|
||||
len(data["latest"]["version"]) if "latest" in data else len("Version")
|
||||
)
|
||||
max_lver_len = lver_len if lver_len > max_lver_len else max_lver_len
|
||||
|
||||
self.max_mod_len = max_mod_len
|
||||
self.max_cver_len = max_cver_len
|
||||
self.max_lver_len = max_lver_len
|
||||
self.max_ver_len = max_lver_len if max_lver_len > max_cver_len else max_cver_len
|
||||
|
||||
def list(self):
|
||||
"""Lists the mods installed on this server."""
|
||||
# Find the longest mod name
|
||||
|
||||
print(
|
||||
"{:<{width}}\tenabled\tinstalled\tcurrent_v\tlatest_v".format(
|
||||
"mod_name", width=self.max_mod_len
|
||||
)
|
||||
)
|
||||
for mod, data in self.mods.items():
|
||||
print(
|
||||
"{:<{width}}\t{enbld}\t{inst}\t\t{cver}\t\t{lver}".format(
|
||||
mod,
|
||||
enbld=str(data["enabled"]),
|
||||
inst=str(data["installed"]),
|
||||
cver=data["version"] if data["installed"] else "N/A",
|
||||
lver=data["latest"]["version"] if "latest" in data else "N/A",
|
||||
width=self.max_mod_len,
|
||||
)
|
||||
)
|
||||
|
||||
def override_credentials(self, username: str, token: str):
|
||||
"""Replaces the values provided in server-settings.json or player-data.json"""
|
||||
if username is not None:
|
||||
self.username = username
|
||||
if token is not None:
|
||||
self.token = token
|
||||
|
||||
def _print_mod_message(
|
||||
self, mod: str, version: str, action: str, result: str, message: str, data: hash
|
||||
):
|
||||
"""
|
||||
Prints a mod status message using the provided parameters.
|
||||
"""
|
||||
if data is not None:
|
||||
title = data["title"] if self.title_mode else mod
|
||||
else:
|
||||
title = mod
|
||||
|
||||
output_string = (
|
||||
"{title:<{mwidth}}\t{version:<{vwidth}}"
|
||||
"\t{action:<10}\t{result:<10}\t{message}"
|
||||
).format(
|
||||
title=title,
|
||||
version=version,
|
||||
action=action,
|
||||
result=result,
|
||||
message=message,
|
||||
vwidth=self.max_ver_len,
|
||||
mwidth=self.max_mod_len,
|
||||
)
|
||||
print(output_string)
|
||||
|
||||
def update(self):
|
||||
"""
|
||||
Updates all mods currently installed on this server to the latest
|
||||
release
|
||||
"""
|
||||
self._print_mod_message("Mod", "Version", "Action", "Result", "Message", None)
|
||||
|
||||
for mod, data in self.mods.items():
|
||||
version = data["version"] if data["installed"] else "N/A"
|
||||
if "metadata" not in data:
|
||||
self._print_mod_message(
|
||||
mod=mod,
|
||||
version=version,
|
||||
action="Skip",
|
||||
result="N/A",
|
||||
message="Missing metadata, skipping update!",
|
||||
data=data,
|
||||
)
|
||||
continue
|
||||
if "latest" not in data:
|
||||
message = (
|
||||
"No release found for factorio '{version}', skipping update!"
|
||||
).format(version=self.fact_version["release"])
|
||||
self._print_mod_message(
|
||||
mod=mod,
|
||||
version=version,
|
||||
action="Skip",
|
||||
result="N/A",
|
||||
message=message,
|
||||
data=data,
|
||||
)
|
||||
continue
|
||||
|
||||
self._prune_old_releases(mod)
|
||||
self._download_latest_release(mod)
|
||||
|
||||
# Update the mod list file
|
||||
self._update_mod_list()
|
||||
|
||||
def _prune_old_releases(self, mod: str):
|
||||
"""
|
||||
Deletes any locally installed versions older than the latest release.
|
||||
|
||||
Keyword Arguments:
|
||||
mod -- name of the target to update
|
||||
"""
|
||||
data = self.mods[mod]
|
||||
latest_version = data["latest"]["version"]
|
||||
|
||||
# Declare the patterns
|
||||
mod_pattern = re.compile(
|
||||
"^{mod}_({ver})[.]zip$".format(mod=mod, ver=self.MOD_VERSION_PATTERN)
|
||||
)
|
||||
version_pattern = re.compile(
|
||||
"^{mod}_{ver}[.]zip$".format(mod=mod, ver=latest_version)
|
||||
)
|
||||
|
||||
# Build the parse list
|
||||
basenames = [os.path.basename(x) for x in self.mod_files]
|
||||
inst_rels = [x for x in basenames if mod_pattern.fullmatch(x)]
|
||||
for rel in inst_rels:
|
||||
if version_pattern.fullmatch(rel):
|
||||
continue
|
||||
|
||||
match = mod_pattern.fullmatch(rel)
|
||||
if match:
|
||||
rel_ver = match.group(1)
|
||||
else:
|
||||
rel_ver = "TBD"
|
||||
|
||||
rel_path = os.path.join(self.mod_path, rel)
|
||||
try:
|
||||
os.remove(rel_path)
|
||||
result = "Success"
|
||||
message = ""
|
||||
except OSError as error:
|
||||
message = ("error: failed to remove '{fname}': " "{errstr}").format(
|
||||
fname=rel_path, errstr=error.strerror
|
||||
)
|
||||
result = "Failure"
|
||||
|
||||
self._print_mod_message(
|
||||
mod=mod,
|
||||
version=rel_ver,
|
||||
action="Remove",
|
||||
result=result,
|
||||
message=message,
|
||||
data=data,
|
||||
)
|
||||
|
||||
def _download_latest_release(self, mod: str):
|
||||
"""
|
||||
Retrieves the latest version of the specified mod compatible with the
|
||||
factorio release present on this server.
|
||||
|
||||
Keyword Arguments:
|
||||
mod -- name of the target to update
|
||||
"""
|
||||
data = self.mods[mod]
|
||||
latest = data["latest"]
|
||||
target = os.path.join(self.mod_path, latest["file_name"])
|
||||
|
||||
validate = download = False
|
||||
|
||||
v_cur = data["version"] if "version" in data else "N/A"
|
||||
v_new = latest["version"]
|
||||
if data["installed"]:
|
||||
if v_new == v_cur:
|
||||
validate = True
|
||||
else:
|
||||
message = "Updating from '{v_cur}'".format(v_cur=v_cur)
|
||||
download = True
|
||||
else:
|
||||
message = "Downloading initial release '{v_new}'".format(v_new=v_new)
|
||||
download = True
|
||||
|
||||
if validate:
|
||||
if _validate_hash(latest["sha1"], target):
|
||||
result = "Success"
|
||||
message = "Deprecated mod" if data["deprecated"] else ""
|
||||
else:
|
||||
result = "Failure"
|
||||
download = True
|
||||
message = "Validation failed, downloading again"
|
||||
self._print_mod_message(
|
||||
mod=mod,
|
||||
version=v_cur,
|
||||
action="Validate",
|
||||
result=result,
|
||||
message=message,
|
||||
data=data,
|
||||
)
|
||||
|
||||
if download:
|
||||
creds = {"username": self.username, "token": self.token}
|
||||
dl_url = self.mod_server_url + latest["download_url"]
|
||||
with requests.get(dl_url, params=creds, stream=True) as req:
|
||||
if req.status_code == 200:
|
||||
with open(target, "wb") as target_file:
|
||||
shutil.copyfileobj(req.raw, target_file)
|
||||
target_file.flush()
|
||||
if _validate_hash(latest["sha1"], target):
|
||||
result = "Success"
|
||||
else:
|
||||
result = "Failure"
|
||||
message = "Download did not match checksum!"
|
||||
elif req.status_code == 403:
|
||||
message = (
|
||||
"Failed to download, credentials not accepted. "
|
||||
+ "Check your username/token"
|
||||
)
|
||||
result = "Failure"
|
||||
else:
|
||||
message = "Unable to retrieve, status code: " + str(req.status_code)
|
||||
result = "Failure"
|
||||
|
||||
self._print_mod_message(
|
||||
mod=mod,
|
||||
version=v_new,
|
||||
action="Download",
|
||||
result=result,
|
||||
message=message,
|
||||
data=data,
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
DESC_TEXT = "Updates mods for a target factorio installation"
|
||||
PARSER = argparse.ArgumentParser(description=DESC_TEXT)
|
||||
# Username
|
||||
PARSER.add_argument(
|
||||
"-u",
|
||||
"--username",
|
||||
dest="username",
|
||||
help="factorio.com username overriding server-settings.json/player-data.json",
|
||||
)
|
||||
# Token
|
||||
PARSER.add_argument(
|
||||
"-t",
|
||||
"--token",
|
||||
dest="token",
|
||||
help="factorio.com API token overriding server-settings.json/player-data.json",
|
||||
)
|
||||
# Title format
|
||||
PARSER.add_argument(
|
||||
"--print-titles",
|
||||
dest="title_mode",
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="When true, print the mod title instead of the api name",
|
||||
)
|
||||
# Server Settings
|
||||
PARSER.add_argument(
|
||||
"-s",
|
||||
"--server-settings",
|
||||
dest="settings_path",
|
||||
required=False,
|
||||
help=(
|
||||
"Absolute path to the server-settings.json file "
|
||||
+ "(overrides player-data.json)"
|
||||
),
|
||||
)
|
||||
# Player Data
|
||||
PARSER.add_argument(
|
||||
"-d",
|
||||
"--player-data",
|
||||
dest="data_path",
|
||||
required=False,
|
||||
help="Absolute path to the player-data.json file",
|
||||
)
|
||||
# Factorio mod directory
|
||||
PARSER.add_argument(
|
||||
"-m",
|
||||
"--mod-directory",
|
||||
dest="mod_path",
|
||||
required=True,
|
||||
help="Absolute path to the mod directory",
|
||||
)
|
||||
# Factorio binary absolute path
|
||||
PARSER.add_argument(
|
||||
"--fact-path",
|
||||
dest="fact_path",
|
||||
required=True,
|
||||
help="Absolute path to the factorio binary",
|
||||
)
|
||||
# Possible Execution modes
|
||||
MODE_GROUP = PARSER.add_mutually_exclusive_group(required=True)
|
||||
MODE_GROUP.add_argument(
|
||||
"--list",
|
||||
dest="mode",
|
||||
action="store_const",
|
||||
const=ModUpdater.Mode.LIST,
|
||||
help="List the currently installed mods with versions",
|
||||
)
|
||||
MODE_GROUP.add_argument(
|
||||
"--update",
|
||||
dest="mode",
|
||||
action="store_const",
|
||||
const=ModUpdater.Mode.UPDATE,
|
||||
help="Update all mods to their latest release",
|
||||
)
|
||||
|
||||
ARGS = PARSER.parse_args()
|
||||
UPDATER = ModUpdater(
|
||||
settings_path=ARGS.settings_path,
|
||||
data_path=ARGS.data_path,
|
||||
mod_path=ARGS.mod_path,
|
||||
fact_path=ARGS.fact_path,
|
||||
creds={"username": ARGS.username, "token": ARGS.token},
|
||||
title_mode=ARGS.title_mode,
|
||||
)
|
||||
|
||||
if ARGS.mode == ModUpdater.Mode.LIST:
|
||||
UPDATER.list()
|
||||
elif ARGS.mode == ModUpdater.Mode.UPDATE:
|
||||
UPDATER.update()
|
Loading…
Reference in New Issue