Added folder selection dialog to options menu. Fixed bug where options were not being saved to the correct path.
359 lines
15 KiB
Python
359 lines
15 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(__file__)
|
|
|
|
|
|
class Form(QtW.QMainWindow):
|
|
def __init__(self):
|
|
QtW.QMainWindow.__init__(self)
|
|
self.setMinimumSize(500, 600)
|
|
self.setWindowIcon(QtG.QIcon(os.path.join(base_path, "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("")
|
|
self.mod_title_edit.textChanged.connect(self.update_slug)
|
|
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 update_slug(self):
|
|
self.mod_slug_edit.setText(slugify(self.mod_title_edit.text(),
|
|
replacements=[["&", "and"], ["$", "s"], ["'", ""]]))
|
|
|
|
def file_dropped(self, file_path: str):
|
|
self.jar_path = file_path
|
|
self.btn_process.setText("Process")
|
|
if config["extract"]:
|
|
jar = zipfile.ZipFile(file_path, "r")
|
|
loaders = []
|
|
if "META-INF/mods.toml" in jar.namelist():
|
|
# This jar file uses FML
|
|
logging.debug("identified mod as forge mod")
|
|
loaders.append("forge")
|
|
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.get("mods", [{}])[0].get("version") == "${file.jarVersion}":
|
|
try:
|
|
mod_toml["mods"][0]["version"] = re.findall(
|
|
r"Implementation-Version: (.*)", jar.read("META-INF/MANIFEST.MF").decode("utf-8"))[0]
|
|
except IndexError:
|
|
mod_toml["mods"][0]["version"] = "0.0.0"
|
|
|
|
# Update form with data
|
|
self.mod_title_edit.setText(mod_toml.get("mods", [{}])[0].get("displayName", "").strip())
|
|
self.mod_slug_edit.setText(slugify(self.mod_title_edit.text(),
|
|
replacements=[["&", "and"], ["$", "s"], ["'", ""]]).strip())
|
|
self.mod_version_edit.setText(mod_toml.get("mods", [{}])[0].get("version", "").strip())
|
|
self.mod_author_edit.setText(mod_toml.get("mods", [{}])[0].get("authors", "").strip())
|
|
self.mod_url_edit.setText(mod_toml.get("mods", [{}])[0].get("displayURL", "").strip())
|
|
self.mod_desc_edit.setPlainText(mod_toml.get("mods", [{}])[0].get("description", "").strip())
|
|
if "fabric.mod.json" in jar.namelist():
|
|
# This jar file uses Fabric
|
|
logging.debug("identified mod as fabric mod")
|
|
loaders.append("fabric")
|
|
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", "").strip())
|
|
self.mod_slug_edit.setText(slugify(mod_json.get("name", ""),
|
|
replacements=[["&", "and"], ["$", "s"], ["'", ""]]))
|
|
self.mod_version_edit.setText(mod_json.get("version", "").strip())
|
|
|
|
# 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).strip())
|
|
|
|
self.mod_url_edit.setText(mod_json.get("contact", {}).get("homepage", "").strip())
|
|
self.mod_desc_edit.setPlainText(mod_json.get("description", "").strip())
|
|
if "quilt.mod.json" in jar.namelist():
|
|
# This jar file uses Quilt
|
|
logging.debug("identified mod as quilt mod")
|
|
loaders.append("quilt")
|
|
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", "").strip())
|
|
self.mod_slug_edit.setText(slugify(mod_json.get("quilt_loader", {}).get("metadata", {}).get("name", ""),
|
|
replacements=[["&", "and"], ["$", "s"], ["'", ""]]).strip())
|
|
self.mod_version_edit.setText(mod_json.get("quilt_loader", {}).get("version", "").strip())
|
|
self.mod_author_edit.setText(", ".join(
|
|
mod_json.get("quilt_loader", {}).get("metadata", {}).get("contributors", {}).keys()).strip())
|
|
self.mod_url_edit.setText(
|
|
mod_json.get("quilt_loader", {}).get("metadata", {}).get("contact", {}).get("homepage", "").strip())
|
|
self.mod_desc_edit.setPlainText(
|
|
mod_json.get("quilt_loader", {}).get("metadata", {}).get("description", "").strip())
|
|
self.mod_loader_edit.setText("+".join(loaders))
|
|
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("Finished")
|
|
|
|
def failed_processing(self, err):
|
|
dialog = QtW.QMessageBox(self)
|
|
dialog.setWindowTitle("Error")
|
|
dialog.setText("There was an error processing this JAR.")
|
|
dialog.exec()
|
|
|
|
logging.debug(err)
|
|
self.btn_process.setEnabled(True)
|
|
self.btn_process.setText("Failed")
|
|
|
|
|
|
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(400, 200)
|
|
|
|
self.setWindowIcon(QtG.QIcon(os.path.join(base_path, "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.btn_export_folder = QtW.QPushButton("Browse")
|
|
self.btn_export_folder.clicked.connect(self.select_folder)
|
|
layout.addWidget(self.btn_export_folder, 1, 3)
|
|
|
|
self.input_technic_url = QtW.QLineEdit(config["url"])
|
|
layout.addWidget(self.input_technic_url, 2, 2, 1, 2)
|
|
self.input_technic_cookie = QtW.QLineEdit(config["token"])
|
|
layout.addWidget(self.input_technic_cookie, 3, 2, 1, 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, 1, 2)
|
|
|
|
button_save = QtW.QPushButton("Save")
|
|
layout.addWidget(button_save, 5, 1, 1, 3)
|
|
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(os.path.join(os.getcwd(), "config.toml"), "w") as configFile:
|
|
toml.dump(config, configFile)
|
|
configFile.close()
|
|
self.close()
|
|
|
|
def select_folder(self):
|
|
self.input_export_folder.setText(str(QtW.QFileDialog.getExistingDirectory(self, "Select Folder")))
|
|
|
|
@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(os.path.join(os.getcwd(), "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(os.path.join(os.getcwd(), "config.toml"), "w") as f:
|
|
toml.dump(config, f)
|
|
f.close()
|
|
|
|
app = QtW.QApplication(sys.argv)
|
|
form = Form()
|
|
form.show()
|
|
sys.exit(app.exec())
|