-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5 from Advik-B/issue-4-Add_CLI_support
Issue 4 add cli support
- Loading branch information
Showing
11 changed files
with
697 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,4 @@ | ||
*.env | ||
*.env | ||
*.jar | ||
*.log | ||
build/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
from PyQt5.QtWidgets import QMainWindow, QApplication, QLabel, QLineEdit, QCheckBox, QProgressBar, QListWidget, QPushButton, QFileDialog, QTextEdit | ||
from PyQt5 import uic | ||
from PyQt5.QtCore import QThread | ||
from logger import Logger | ||
from threading import Thread | ||
from backend import ModPack | ||
import sys | ||
import os | ||
|
||
class UI(QMainWindow): | ||
|
||
def __init__(self): | ||
super(UI, self).__init__() | ||
# Set up the logger | ||
self.logger = logger | ||
|
||
# Load the UI | ||
uic.loadUi("design.ui", self) | ||
# Set up the window title and make it non-resizable | ||
self.setWindowTitle("CMPDL by Advik-B") | ||
self.setMaximumSize(self.size()) | ||
# Define widgets from ui file | ||
self.title_lbl = self.findChild(QLabel, "title_lbl") | ||
self.modpack_pth = self.findChild(QLineEdit, "modpack_pth") | ||
self.output_dir = self.findChild(QLineEdit, "download_pth") | ||
self.optional_mods = self.findChild(QCheckBox, "optional_mods") | ||
self.keep_config = self.findChild(QCheckBox, "keep_config") | ||
self.overall_progress = self.findChild(QProgressBar, "progress") | ||
self.per_mod_progress = self.findChild(QProgressBar, "per_mod_progress") | ||
self.mod_list = self.findChild(QListWidget, "DownloadList") | ||
self.log_box = self.findChild(QTextEdit, "logbox") | ||
self.start_download = self.findChild(QPushButton, "start_download_btn") | ||
self.copy_logs = self.findChild(QPushButton, "copy_logs_btn") | ||
self.browse_modpack = self.findChild(QPushButton, "browse_modpack") | ||
self.output_dir_btn = self.findChild(QPushButton, "download_pth_browse") | ||
# Create a list of widgets, this is used to check if all widgets are found in the ui file and be deleted later to free up memory | ||
self.widgets = [ | ||
|
||
self.title_lbl, | ||
self.modpack_pth, | ||
self.output_dir, | ||
self.optional_mods, | ||
self.keep_config, | ||
self.overall_progress, | ||
self.per_mod_progress, | ||
self.mod_list, | ||
self.log_box, | ||
self.start_download, | ||
self.copy_logs, | ||
self.browse_modpack, | ||
self.output_dir_btn, | ||
] | ||
# Check if all widgets are defined properly | ||
# if the code below works then all widgets are defined properly | ||
try: | ||
self.log("Checking widgets", "info") | ||
self.check_widgets() | ||
except AssertionError as e: | ||
self.log("Widget not found: %s" % e, "error") | ||
sys.exit(1) | ||
# set up the log box | ||
self.log_box.setReadOnly(True) | ||
|
||
# Connect the buttons to their functions | ||
self.browse_modpack.clicked.connect(self.browse__modpack) | ||
self.output_dir_btn.clicked.connect(self.browse_output_dir) | ||
self.copy_logs.clicked.connect(self.copy_logs_func) | ||
self.start_download.clicked.connect(self.start_payload) | ||
# show the window | ||
self.show() | ||
self.log("Window sucessfully loaded", "info") | ||
|
||
def log(self, message: str, type_: str): | ||
|
||
msg = self.logger.log(message, type_) | ||
try: | ||
|
||
self.log_box.setReadOnly(False) | ||
self.log_box.setText(self.log_box.toPlainText() + str(msg)+ '\n') | ||
self.log_box.setReadOnly(True) | ||
except AttributeError: | ||
self.logger.log("Log box not found", "error") | ||
|
||
def check_widgets(self): | ||
for widget in self.widgets: | ||
self.log(widget, "debug") | ||
assert widget is not None, "Widget not found" | ||
# Free up memory by deleting the list | ||
del self.widgets | ||
|
||
def browse__modpack(self): | ||
"""Browse for a .json / .zip file""" | ||
file_ = QFileDialog.getOpenFileName( | ||
self, | ||
caption="Select modpack or a manifest file", | ||
filter="Manifest (*.json);;ModPack (*.zip)", | ||
initialFilter="ModPack (*.zip)", | ||
directory=os.path.expanduser("~"), | ||
) | ||
|
||
if file_[0]: | ||
self.modpack_pth.setText(file_[0]) | ||
self.log("Modpack path set to: %s" % file_[0], "info") | ||
|
||
def browse_output_dir(self): | ||
"""Browse for a directory""" | ||
if directory := QFileDialog.getExistingDirectory( | ||
self, | ||
caption="Select output directory", | ||
): | ||
self.output_dir.setText(directory) | ||
self.log("Output directory set to: %s" % directory, "info") | ||
|
||
def copy_logs_func(self): | ||
"""Copy the logs to the clipboard""" | ||
self.log_box.selectAll() | ||
self.log_box.copy() | ||
self.log_box.undo() | ||
self.log("Logs copied to clipboard", "info") | ||
|
||
def secondry_log(self, message: str): | ||
"""Secondry log function""" | ||
self.log(message, "info") | ||
self.mod_list.addItem(message) | ||
|
||
def start_payload(self): | ||
try: | ||
kwargs = { | ||
|
||
'log_func': self.log, | ||
'secondry_log': self.secondry_log, | ||
'pbar': self.overall_progress, | ||
'pbar2': self.per_mod_progress, | ||
'output_dir': self.output_dir.text(), | ||
'download_optional_mods': self.optional_mods.isChecked(), | ||
'keep_config': self.keep_config.isChecked(), | ||
|
||
} | ||
|
||
self.log("Starting payload", "info") | ||
modpack = ModPack(self.modpack_pth.text(), **kwargs) | ||
modpack.init() | ||
t = Thread(target=modpack.install) | ||
t.start() | ||
|
||
except Exception as e: | ||
self.log('Error: %s' % e, 'error') | ||
self.log("Payload failed", "error") | ||
raise e | ||
return | ||
|
||
def main(): | ||
global logger | ||
logger = Logger() | ||
app = QApplication(sys.argv) | ||
UIWindow = UI() | ||
app.setActiveWindow(UIWindow) | ||
app.exec_() | ||
logger.quit() | ||
sys.exit(0) | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
from cursepy import CurseClient | ||
from tree_generator import gentree | ||
from urllib.parse import unquote | ||
from clint.textui import progress | ||
from PyQt5.QtWidgets import QProgressBar | ||
from time import perf_counter as now | ||
import zipfile | ||
import tempfile | ||
import os | ||
import json | ||
import shutil | ||
import requests | ||
|
||
|
||
class ModPack: | ||
def __init__(self, path: str, **kwargs) -> None: | ||
self.log = kwargs.get("log_func") | ||
self.secondry_log = kwargs.get("secondry_log") | ||
# Set up the progress bar | ||
self.progressbar = kwargs.get("pbar") | ||
self.progressbar.setValue(0) | ||
|
||
self.sec_progressbar = QProgressBar(kwargs.get("pbar2")) | ||
self.output_dir = kwargs.get("output_dir") | ||
self.download_optional = kwargs.get("download_optional_mods") | ||
self.keep_config = kwargs.get("keep_config") | ||
|
||
self.ini = False | ||
self.path = path | ||
|
||
def step(self, pbar: QProgressBar, value: int): | ||
pbar.setValue(pbar.value() + value) | ||
|
||
def init(self): | ||
self.log("Initializing Curse client...", 'info') | ||
start = now() | ||
self.c = CurseClient() | ||
stop = now() | ||
self.log("Successfully initialized Curse client in %s seconds" % str(stop - start), | ||
'info') | ||
del stop, start | ||
self.log("Initializing ModPack...", 'info') | ||
start = now() | ||
if self.path.endswith('.zip'): | ||
self.tempdir = tempfile.mkdtemp(prefix='CMPDL') | ||
self.log("Extracting modpack to %s" % self.tempdir, 'info') | ||
with zipfile.ZipFile(self.path, 'r') as z: | ||
z.extractall(self.tempdir) | ||
self.log("Successfully extracted modpack", 'info') | ||
self.log("ModPack file structure:\n%s" % gentree(self.tempdir), 'info') | ||
self.manifest_path = os.path.join(self.tempdir, 'manifest.json') | ||
self.meth = 'ZIP' | ||
elif self.path.endswith('.json'): | ||
self.manifest_path = self.path | ||
self.meth = 'JSON' | ||
try: | ||
self.log("Manifest path set to %s" % self.manifest_path, 'info') | ||
except AttributeError: | ||
self.log("Manifest path not set", 'info') | ||
|
||
self.log("Loading manifest...", 'info') | ||
with open(self.manifest_path, 'r') as f: | ||
mani = json.load(f) | ||
self.manifest = mani['files'] | ||
self.log("Minecraft version: %s" % mani['minecraft']['version'], 'info') | ||
self.log("Modpack version: %s" % mani['version'], 'info') | ||
self.log("Modpack name: %s" % mani['name'], 'info') | ||
self.log("Modpack author(s): %s" % mani['author'], 'info') | ||
self.log("Modpack description: %s" % mani.get('description'), 'info') | ||
self.log("Modpack website: %s" % mani.get('website'), 'info') | ||
self.log("Modpack modloader: %s" % mani['minecraft']["modLoaders"][0]['id'], 'info') | ||
self.log("Modpack config/overrides: %s" % mani['overrides'], 'info') | ||
self.log("Successfully loaded manifest", 'info') | ||
if self.meth == 'ZIP': | ||
self.override_folder = os.path.join(self.tempdir, mani['overrides']) | ||
del mani | ||
stop = now() | ||
self.log("Successfully initialized ModPack in %s seconds" % str(stop - start), 'info') | ||
del stop, start | ||
self.ini = True | ||
|
||
def install(self): | ||
global mods_folder | ||
self.log("Installing ModPack...", 'info') | ||
start = now() | ||
if self.meth == 'ZIP' and os.path.isdir(self.override_folder): | ||
|
||
self.log("Extracting overrides to %s" % self.output_dir, 'info') | ||
for file in os.listdir(self.override_folder): | ||
if os.path.isfile(os.path.join(self.override_folder, file)): | ||
shutil.copyfile(os.path.join(self.override_folder, file), self.output_dir) | ||
else: | ||
try: | ||
shutil.copytree(os.path.join(self.override_folder, file), os.path.join(self.output_dir, file)) | ||
except FileExistsError: | ||
shutil.rmtree(os.path.join(self.output_dir, file)) | ||
shutil.copytree(os.path.join(self.override_folder, file), os.path.join(self.output_dir, file)) | ||
|
||
self.log("Successfully extracted overrides", 'info') | ||
mods_folder = os.path.join(self.output_dir, 'mods') | ||
try: | ||
os.makedirs(mods_folder) | ||
except FileExistsError: | ||
shutil.rmtree(mods_folder) | ||
os.makedirs(mods_folder) | ||
|
||
mods_folder = self.output_dir | ||
self.log("Downloading mods to %s" % mods_folder, 'info') | ||
# Adjust the progress bar(s) | ||
self.progressbar.setValue(0) | ||
self.progressbar.setMaximum(len(self.manifest)) | ||
self.sec_progressbar.setValue(0) | ||
for i, mod_ in enumerate(self.manifest): | ||
mod = self.c.addon(mod_['projectID']) | ||
self.log("Downloading mod %s out of %s" % (i, len(self.manifest)), 'info') | ||
self.log("Mod name: %s" % mod.name, 'info') | ||
self.log("Mod url: %s" % mod.url, 'info') | ||
self.log("Mod author(s): %s" % str(mod.authors), 'info') | ||
if mod_['required']: | ||
self.log("Mod is required", 'info') | ||
file = mod.file(mod_['fileID']) | ||
save_path = os.path.join( | ||
mods_folder, unquote(file.download_url.split('/')[-1] | ||
)) | ||
self.download_raw(file.download_url, save_path, self.sec_progressbar) | ||
else: | ||
self.log("Mod is optional", 'info') | ||
if self.download_optional: | ||
file = mod.file(mod_['fileID']) | ||
save_path = os.path.join( | ||
mods_folder, unquote(file.download_url.split['/'][-1]) | ||
) | ||
self.download_raw(file.download_url, save_path, self.sec_progressbar) | ||
self.sec_progressbar.setValue(0) | ||
self.step(self.progressbar, 1) | ||
self.secondry_log('%s' % mod.name) | ||
stop = now() | ||
self.log("Successfully installed ModPack in %s seconds" % str(stop - start), 'info') | ||
self.clean() | ||
|
||
def download_raw(self, link: str, path: str, pbar: QProgressBar): | ||
if not self.ini: | ||
raise Exception("ModPack not initialized") | ||
|
||
r = requests.get(link, stream=True) | ||
with open(path, 'wb') as f: | ||
self.log('LINK: %s' % link, 'debug') | ||
self.log('PATH: %s' % path, 'debug') | ||
self.log('HEADERS: %s' % r.headers, 'debug') | ||
total_length = int(r.headers.get('content-length')) | ||
self.log('TOTAL LENGTH: %s' % total_length, 'debug') | ||
for chunk in progress.bar(r.iter_content(chunk_size=1024), expected_size=(total_length / 1024) + 1): | ||
if chunk: | ||
f.write(chunk) | ||
f.flush() | ||
self.step(pbar, 1) | ||
self.log('Downloaded %s to %s' % (link, path), 'debug') | ||
|
||
def clean(self): | ||
self.log("Cleaning up...", 'info') | ||
if self.meth == 'ZIP': | ||
shutil.rmtree(self.tempdir, ignore_errors=True) | ||
self.log("Successfully cleaned up", 'info') | ||
self.ini = False |
Oops, something went wrong.