338 lines
14 KiB
Python
338 lines
14 KiB
Python
import json
|
|
import logging
|
|
import os
|
|
import re
|
|
import sys
|
|
import traceback
|
|
import zipfile
|
|
|
|
import PySide6.QtCore as QtC
|
|
import PySide6.QtGui as QtG
|
|
import PySide6.QtWidgets as QtW
|
|
import toml
|
|
from slugify import slugify
|
|
|
|
from solderPacker import SolderPacker
|
|
|
|
# Globals
|
|
config = {}
|
|
base_path = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
|
|
class Form(QtW.QMainWindow):
|
|
def __init__(self):
|
|
QtW.QMainWindow.__init__(self)
|
|
self.setMinimumSize(400, 500)
|
|
self.setWindowIcon(QtG.QIcon(f"{base_path}{os.sep}wrenchIcon.ico"))
|
|
self.setWindowTitle("Solder Packer")
|
|
|
|
self.thread_pool = QtC.QThreadPool()
|
|
self.solder_packer = SolderPacker(config["path"], config["url"], config["token"])
|
|
self.jar_path = ""
|
|
|
|
menubar = self.menuBar()
|
|
options_action = menubar.addAction("Options")
|
|
options_action.triggered.connect(self.open_options)
|
|
|
|
main_widget = QtW.QWidget()
|
|
layout = QtW.QGridLayout()
|
|
|
|
mod_title_label = QtW.QLabel("Mod Name")
|
|
self.mod_title_edit = QtW.QLineEdit("")
|
|
layout.addWidget(mod_title_label, 1, 1)
|
|
layout.addWidget(self.mod_title_edit, 1, 2)
|
|
|
|
mod_slug_label = QtW.QLabel("Mod Slug")
|
|
self.mod_slug_edit = QtW.QLineEdit("")
|
|
layout.addWidget(mod_slug_label, 2, 1)
|
|
layout.addWidget(self.mod_slug_edit, 2, 2)
|
|
|
|
mod_version_label = QtW.QLabel("Mod Version")
|
|
self.mod_version_edit = QtW.QLineEdit("")
|
|
layout.addWidget(mod_version_label, 3, 1)
|
|
layout.addWidget(self.mod_version_edit, 3, 2)
|
|
|
|
mod_mc_version_label = QtW.QLabel("MC Version")
|
|
self.mod_mc_version_edit = QtW.QLineEdit("")
|
|
layout.addWidget(mod_mc_version_label, 4, 1)
|
|
layout.addWidget(self.mod_mc_version_edit, 4, 2)
|
|
|
|
mod_loader_label = QtW.QLabel("Mod Loader")
|
|
self.mod_loader_edit = QtW.QLineEdit("")
|
|
layout.addWidget(mod_loader_label, 5, 1)
|
|
layout.addWidget(self.mod_loader_edit, 5, 2)
|
|
|
|
mod_author_label = QtW.QLabel("Mod Author")
|
|
self.mod_author_edit = QtW.QLineEdit("")
|
|
layout.addWidget(mod_author_label, 6, 1)
|
|
layout.addWidget(self.mod_author_edit, 6, 2)
|
|
|
|
mod_url_label = QtW.QLabel("Mod URL")
|
|
self.mod_url_edit = QtW.QLineEdit("")
|
|
layout.addWidget(mod_url_label, 7, 1)
|
|
layout.addWidget(self.mod_url_edit, 7, 2)
|
|
|
|
mod_desc_label = QtW.QLabel("Mod Description")
|
|
mod_desc_label.setAlignment(QtC.Qt.AlignmentFlag.AlignCenter)
|
|
self.mod_desc_edit = QtW.QPlainTextEdit("")
|
|
self.mod_desc_edit.setMinimumSize(200, 50)
|
|
layout.addWidget(mod_desc_label, 8, 1, 1, 2)
|
|
layout.addWidget(self.mod_desc_edit, 9, 1, 1, 2)
|
|
|
|
horizontal_line = QtW.QFrame()
|
|
horizontal_line.setFrameShape(QtW.QFrame.Shape.HLine)
|
|
horizontal_line.setFrameShadow(QtW.QFrame.Shadow.Sunken)
|
|
layout.addWidget(horizontal_line, 10, 1, 1, 2)
|
|
|
|
self.drop_label = FileDropArea("Drop JAR Here", self)
|
|
self.drop_label.setMinimumSize(200, 50)
|
|
self.drop_label.setAlignment(QtC.Qt.AlignmentFlag.AlignCenter)
|
|
self.drop_label.setFrameStyle(QtW.QFrame.Shadow.Sunken | QtW.QFrame.Shape.StyledPanel)
|
|
self.drop_label.dropped.connect(self.file_dropped)
|
|
layout.addWidget(self.drop_label, 11, 1, 1, 2)
|
|
|
|
self.btn_process = QtW.QPushButton("Process")
|
|
self.btn_process.clicked.connect(self.process_click)
|
|
layout.addWidget(self.btn_process, 12, 1, 1, 2)
|
|
|
|
self.setCentralWidget(main_widget)
|
|
main_widget.setLayout(layout)
|
|
|
|
def open_options(self):
|
|
options_dialog = OptionsDialog(parent=self)
|
|
options_dialog.closed.connect(self.options_closed)
|
|
options_dialog.show()
|
|
|
|
def options_closed(self):
|
|
self.solder_packer.solder_dir = config["path"]
|
|
self.solder_packer.solder_url = config["url"]
|
|
self.solder_packer.solder_key = config["token"]
|
|
|
|
def file_dropped(self, file_path: str):
|
|
self.jar_path = file_path
|
|
if config["extract"]:
|
|
jar = zipfile.ZipFile(file_path, "r")
|
|
if "META-INF/mods.toml" in jar.namelist():
|
|
# This jar file uses FML
|
|
logging.debug("identified mod as forge mod")
|
|
mod_toml = toml.loads(jar.read("META-INF/mods.toml").decode("utf-8"))
|
|
# This mess is to catch if the mod is using the Jar version for versioning. It extracts it from the
|
|
# MANIFEST.MF and places it where it should be in the toml.
|
|
if mod_toml["mods"][0]["version"] == "${file.jarVersion}":
|
|
mod_toml["mods"][0]["version"] = re.findall(
|
|
r"Implementation-Version: (.*)", jar.read("META-INF/MANIFEST.MF").decode("utf-8"))[0]
|
|
|
|
# Update form with data
|
|
self.mod_title_edit.setText(mod_toml.get("mods", [{}])[0].get("displayName", ""))
|
|
self.mod_slug_edit.setText(slugify(mod_toml.get("mods", [{}])[0].get("displayName", ""),
|
|
replacements=[["&", "and"], ["$", "s"], ["'", ""]]))
|
|
self.mod_version_edit.setText(mod_toml.get("mods", [{}])[0].get("version", ""))
|
|
self.mod_loader_edit.setText("forge")
|
|
self.mod_author_edit.setText(mod_toml.get("mods", [{}])[0].get("authors", ""))
|
|
self.mod_url_edit.setText(mod_toml.get("mods", [{}])[0].get("displayURL", ""))
|
|
self.mod_desc_edit.setPlainText(mod_toml.get("mods", [{}])[0].get("description", ""))
|
|
elif "fabric.mod.json" in jar.namelist():
|
|
# This jar file uses Fabric
|
|
logging.debug("identified mod as fabric mod")
|
|
mod_json = json.loads(jar.read("fabric.mod.json").decode("utf-8"))
|
|
|
|
# Update form with data
|
|
self.mod_title_edit.setText(mod_json.get("name", ""))
|
|
self.mod_slug_edit.setText(slugify(mod_json.get("name", ""),
|
|
replacements=[["&", "and"], ["$", "s"], ["'", ""]]))
|
|
self.mod_version_edit.setText(mod_json.get("version", ""))
|
|
self.mod_loader_edit.setText("fabric")
|
|
|
|
# Some spaghetti code for dealing with the possibility of authors being a []string or a dict.
|
|
authors = mod_json.get("authors", [])
|
|
author_list = []
|
|
if len(authors) > 0 and type(authors[0]) == dict:
|
|
for author in authors:
|
|
author_list.append(author.get("name"))
|
|
elif len(authors) > 0 and type(authors[0]) == str:
|
|
author_list = authors
|
|
self.mod_author_edit.setText(", ".join(author_list))
|
|
self.mod_url_edit.setText(mod_json.get("contact", {}).get("homepage", ""))
|
|
self.mod_desc_edit.setPlainText(mod_json.get("description", ""))
|
|
elif "quilt.mod.json" in jar.namelist():
|
|
# This jar file uses Quilt
|
|
logging.debug("identified mod as quilt mod")
|
|
mod_json = json.loads(jar.read("quilt.mod.json").decode("utf-8"))
|
|
|
|
# Update form with data
|
|
self.mod_title_edit.setText(mod_json.get("quilt_loader", {}).get("metadata", {}).get("name", ""))
|
|
self.mod_slug_edit.setText(slugify(mod_json.get("quilt_loader", {}).get("metadata", {}).get("name", ""),
|
|
replacements=[["&", "and"], ["$", "s"], ["'", ""]]))
|
|
self.mod_version_edit.setText(mod_json.get("quilt_loader", {}).get("version", ""))
|
|
self.mod_loader_edit.setText("quilt")
|
|
self.mod_author_edit.setText(
|
|
", ".join(mod_json.get("quilt_loader", {}).get("metadata", {}).get("contributors", {}).keys()))
|
|
self.mod_url_edit.setText(
|
|
mod_json.get("quilt_loader", {}).get("metadata", {}).get("contact", {}).get("homepage", ""))
|
|
self.mod_desc_edit.setPlainText(
|
|
mod_json.get("quilt_loader", {}).get("metadata", {}).get("description", ""))
|
|
else:
|
|
logging.debug("failed to identify mod loader")
|
|
jar.close()
|
|
|
|
def process_click(self):
|
|
self.btn_process.setEnabled(False)
|
|
self.btn_process.setText("Processing...")
|
|
|
|
worker = Worker(self.process, self.mod_slug_edit.text(), self.mod_title_edit.text(),
|
|
self.mod_version_edit.text(), self.mod_mc_version_edit.text(), self.mod_loader_edit.text(),
|
|
self.mod_author_edit.text(), self.mod_url_edit.text(), self.mod_desc_edit.toPlainText(),
|
|
self.jar_path)
|
|
|
|
worker.signals.finished.connect(self.finished_processing)
|
|
worker.signals.error.connect(self.failed_processing)
|
|
self.thread_pool.start(worker)
|
|
|
|
def process(self, mod_slug: str, mod_title: str, mod_version: str, mod_mc_version: str, mod_loader: str,
|
|
mod_author: str, mod_url: str, mod_desc: str, jar_path: str):
|
|
logging.debug("attempting jar compression")
|
|
self.solder_packer.compress_jar(mod_slug, mod_mc_version, mod_loader, mod_version, jar_path)
|
|
logging.debug("jar compression done")
|
|
if config["upload"]:
|
|
logging.debug("attempting to register mod with solder")
|
|
self.solder_packer.register_mod(mod_slug, mod_title, mod_author, mod_desc, mod_url)
|
|
logging.debug("registration complete")
|
|
logging.debug("attempting to register mod version with solder")
|
|
self.solder_packer.register_mod_version(mod_slug, mod_mc_version, mod_loader, mod_version)
|
|
logging.debug("registration complete")
|
|
|
|
def finished_processing(self):
|
|
self.btn_process.setEnabled(True)
|
|
self.btn_process.setText("Process")
|
|
|
|
def failed_processing(self):
|
|
dialog = QtW.QMessageBox(self)
|
|
dialog.setWindowTitle("Error")
|
|
dialog.setText("There was an error processing this JAR.")
|
|
dialog.exec()
|
|
|
|
self.finished_processing()
|
|
|
|
|
|
class FileDropArea(QtW.QLabel):
|
|
dropped = QtC.Signal(str)
|
|
|
|
def __init__(self, title, parent):
|
|
super(FileDropArea, self).__init__(title, parent)
|
|
self.parent = parent
|
|
self.setAcceptDrops(True)
|
|
|
|
def dragEnterEvent(self, e):
|
|
e.accept()
|
|
|
|
@QtC.Slot()
|
|
def dropEvent(self, e):
|
|
file_path = e.mimeData().urls()[0].toLocalFile()
|
|
self.dropped.emit(file_path)
|
|
self.setText(os.path.basename(file_path))
|
|
|
|
|
|
class OptionsDialog(QtW.QDialog):
|
|
closed = QtC.Signal()
|
|
|
|
def __init__(self, parent=None):
|
|
QtW.QDialog.__init__(self, parent)
|
|
|
|
self.setMinimumSize(300, 200)
|
|
|
|
self.setWindowIcon(QtG.QIcon(f"{base_path}{os.sep}wrenchIcon.ico"))
|
|
self.setWindowTitle("Options")
|
|
|
|
layout = QtW.QGridLayout()
|
|
self.setLayout(layout)
|
|
|
|
layout.addWidget(QtW.QLabel("Export Folder"), 1, 1)
|
|
layout.addWidget(QtW.QLabel("Technic URL"), 2, 1)
|
|
layout.addWidget(QtW.QLabel("Technic Cookie"), 3, 1)
|
|
|
|
self.input_export_folder = QtW.QLineEdit(config["path"])
|
|
layout.addWidget(self.input_export_folder, 1, 2)
|
|
self.input_technic_url = QtW.QLineEdit(config["url"])
|
|
layout.addWidget(self.input_technic_url, 2, 2)
|
|
self.input_technic_cookie = QtW.QLineEdit(config["token"])
|
|
layout.addWidget(self.input_technic_cookie, 3, 2)
|
|
|
|
self.checkbox_extract_data = QtW.QCheckBox("Extract Mod Info")
|
|
self.checkbox_extract_data.setChecked(config["extract"])
|
|
layout.addWidget(self.checkbox_extract_data, 4, 1)
|
|
|
|
self.checkbox_upload_data = QtW.QCheckBox("Upload Data")
|
|
self.checkbox_upload_data.setChecked(config["upload"])
|
|
layout.addWidget(self.checkbox_upload_data, 4, 2)
|
|
|
|
button_save = QtW.QPushButton("Save")
|
|
layout.addWidget(button_save, 5, 1, 1, 2)
|
|
button_save.clicked.connect(self.save_options)
|
|
|
|
def save_options(self):
|
|
config["path"] = self.input_export_folder.text()
|
|
config["url"] = self.input_technic_url.text()
|
|
config["token"] = self.input_technic_cookie.text()
|
|
config["extract"] = (self.checkbox_extract_data.checkState() == QtC.Qt.CheckState.Checked)
|
|
config["upload"] = (self.checkbox_upload_data.checkState() == QtC.Qt.CheckState.Checked)
|
|
|
|
with open(f"{base_path}{os.sep}config.toml", "w") as f:
|
|
toml.dump(config, f)
|
|
f.close()
|
|
self.close()
|
|
|
|
@QtC.Slot()
|
|
def closeEvent(self, event):
|
|
self.closed.emit()
|
|
|
|
|
|
class WorkerSignals(QtC.QObject):
|
|
finished = QtC.Signal()
|
|
error = QtC.Signal(tuple)
|
|
|
|
|
|
class Worker(QtC.QRunnable):
|
|
def __init__(self, fn, *args, **kwargs):
|
|
super().__init__()
|
|
self.fn = fn
|
|
self.args = args
|
|
self.kwargs = kwargs
|
|
self.signals = WorkerSignals()
|
|
|
|
@QtC.Slot()
|
|
def run(self):
|
|
try:
|
|
self.fn(*self.args, **self.kwargs)
|
|
except:
|
|
exec_type, value = sys.exc_info()[:2]
|
|
self.signals.error.emit((exec_type, value, traceback.format_exc()))
|
|
finally:
|
|
self.signals.finished.emit()
|
|
|
|
|
|
def get_default(struct: dict, key: str, default: any):
|
|
return default if key not in struct else struct[key]
|
|
|
|
|
|
if __name__ == '__main__':
|
|
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
|
|
|
|
try:
|
|
config = toml.load(f"{base_path}{os.sep}config.toml")
|
|
except FileNotFoundError:
|
|
config = {}
|
|
|
|
config["path"] = get_default(config, "path", os.getcwd())
|
|
config["url"] = get_default(config, "url", os.environ.get("SOLDER_URL") or "http://localhost")
|
|
config["token"] = get_default(config, "token", os.environ.get("SOLDER_KEY") or "")
|
|
config["extract"] = get_default(config, "extract", True)
|
|
config["upload"] = get_default(config, "upload", False)
|
|
with open(f"{base_path}{os.sep}config.toml", "w") as f:
|
|
toml.dump(config, f)
|
|
f.close()
|
|
|
|
app = QtW.QApplication(sys.argv)
|
|
form = Form()
|
|
form.show()
|
|
sys.exit(app.exec())
|