mirror of
https://github.com/Faraphel/Atlas-Install.git
synced 2025-07-03 03:08:29 +02:00
when pressing the install button, everything but the help menu is disabled. Added progress_function to start a function that yield data about the installation to show it on the progress bar
This commit is contained in:
parent
f9db2e96ea
commit
753be7df0d
3 changed files with 190 additions and 10 deletions
|
@ -1,3 +1,7 @@
|
|||
from threading import Thread
|
||||
from typing import Callable
|
||||
|
||||
|
||||
__version__ = (0, 12, 0)
|
||||
__author__ = 'Faraphel'
|
||||
|
||||
|
@ -10,3 +14,17 @@ Mo = 1_000 * Ko
|
|||
Go = 1_000 * Mo
|
||||
|
||||
minimum_space_available = 15*Go
|
||||
|
||||
|
||||
def threaded(func: Callable) -> Callable:
|
||||
"""
|
||||
Decorate a function to run in a separate thread
|
||||
:param func: a function
|
||||
:return: the decorated function
|
||||
"""
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
# run the function in a Daemon, so it will stop when the main thread stops
|
||||
Thread(target=func, args=args, kwargs=kwargs, daemon=True).start()
|
||||
|
||||
return wrapper
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import enum
|
||||
import shutil
|
||||
import tkinter
|
||||
from pathlib import Path
|
||||
|
@ -6,13 +7,30 @@ from tkinter import ttk
|
|||
from tkinter import filedialog
|
||||
from tkinter import messagebox
|
||||
import webbrowser
|
||||
from typing import Generator
|
||||
|
||||
from source.mkw.Game import Game
|
||||
from source.translation import translate as _
|
||||
from source import event
|
||||
from source import *
|
||||
import os
|
||||
|
||||
|
||||
class SourceGameError(Exception):
|
||||
def __init__(self, path: Path | str):
|
||||
super().__init__(f"Invalid path for source game : {path}")
|
||||
|
||||
|
||||
class DestinationGameError(Exception):
|
||||
def __init__(self, path: Path | str):
|
||||
super().__init__(f"Invalid path for destination game : {path}")
|
||||
|
||||
|
||||
class InstallerState(enum.Enum):
|
||||
IDLE = 0
|
||||
INSTALLING = 1
|
||||
|
||||
|
||||
# Main window for the installer
|
||||
class Window(tkinter.Tk):
|
||||
def __init__(self):
|
||||
|
@ -42,6 +60,8 @@ class Window(tkinter.Tk):
|
|||
self.progress_bar = ProgressBar(self)
|
||||
self.progress_bar.grid(row=5, column=1, sticky="nsew")
|
||||
|
||||
self.set_state(InstallerState.IDLE)
|
||||
|
||||
def run(self) -> None:
|
||||
"""
|
||||
Run the installer
|
||||
|
@ -58,6 +78,24 @@ class Window(tkinter.Tk):
|
|||
"""
|
||||
return None
|
||||
|
||||
def set_state(self, state: InstallerState) -> None:
|
||||
"""
|
||||
Set the progress bar state when the installer change state
|
||||
:param state: state of the installer
|
||||
:return:
|
||||
"""
|
||||
for child in self.winfo_children():
|
||||
getattr(child, "set_state", lambda *_: "pass")(state)
|
||||
|
||||
def progress_function(self, func_gen: Generator) -> None:
|
||||
"""
|
||||
Run a generator function that yield status for the progress bar
|
||||
:return:
|
||||
"""
|
||||
# get the generator data yield by the generator function
|
||||
for step_data in func_gen:
|
||||
if "desc" in step_data: self.progress_bar.set_description(step_data["desc"])
|
||||
|
||||
|
||||
# Menu bar
|
||||
class Menu(tkinter.Menu):
|
||||
|
@ -101,14 +139,39 @@ class Menu(tkinter.Menu):
|
|||
super().__init__(master, tearoff=False)
|
||||
|
||||
master.add_cascade(label="Help", menu=self)
|
||||
self.menu_id = self.master.index(tkinter.END)
|
||||
|
||||
self.add_command(label="Discord", command=lambda: webbrowser.open(discord_url))
|
||||
self.add_command(label="Github Wiki", command=lambda: webbrowser.open(github_wiki_url))
|
||||
|
||||
def set_installation_state(self, state: InstallerState) -> bool:
|
||||
"""
|
||||
Set the installation state of the installer
|
||||
:param state: The state to set the installer to
|
||||
:return: True if the state was set, False if not
|
||||
"""
|
||||
|
||||
def set_state(self, state: InstallerState) -> None:
|
||||
"""
|
||||
Set the progress bar state when the installer change state
|
||||
:param state: state of the installer
|
||||
:return:
|
||||
"""
|
||||
# get the last child id of the menu
|
||||
|
||||
for child_id in range(1, self.index(tkinter.END) + 1):
|
||||
# don't modify the state of the help menu
|
||||
if child_id == self.help.menu_id: continue
|
||||
|
||||
match state:
|
||||
case state.IDLE: self.entryconfigure(child_id, state=tkinter.NORMAL)
|
||||
case state.INSTALLING: self.entryconfigure(child_id, state=tkinter.DISABLED)
|
||||
|
||||
|
||||
# Select game frame
|
||||
class SourceGame(ttk.LabelFrame):
|
||||
def __init__(self, master: tkinter.Tk):
|
||||
super().__init__(master, text="Original Game")
|
||||
super().__init__(master, text="Original Game File")
|
||||
|
||||
self.entry = ttk.Entry(self, width=50)
|
||||
self.entry.grid(row=1, column=1, sticky="nsew")
|
||||
|
@ -141,13 +204,34 @@ class SourceGame(ttk.LabelFrame):
|
|||
self.entry.delete(0, tkinter.END)
|
||||
self.entry.insert(0, str(path.absolute()))
|
||||
|
||||
self.master.destination_game.set_path(path.parent / "MKWF.iso")
|
||||
self.master.destination_game.set_path(path.parent)
|
||||
|
||||
def get_path(self) -> Path:
|
||||
"""
|
||||
Get the source game path
|
||||
:return: the game path
|
||||
"""
|
||||
path = Path(self.entry.get())
|
||||
if not path.exists(): raise SourceGameError(path)
|
||||
return path
|
||||
|
||||
|
||||
def set_state(self, state: InstallerState) -> None:
|
||||
"""
|
||||
Set the progress bar state when the installer change state
|
||||
:param state: state of the installer
|
||||
:return:
|
||||
"""
|
||||
for child in self.winfo_children():
|
||||
match state:
|
||||
case InstallerState.IDLE: child.config(state="normal")
|
||||
case InstallerState.INSTALLING: child.config(state="disabled")
|
||||
|
||||
|
||||
# Select game destination frame
|
||||
class DestinationGame(ttk.LabelFrame):
|
||||
def __init__(self, master: tkinter.Tk):
|
||||
super().__init__(master, text="Game Destination")
|
||||
super().__init__(master, text="Game Directory Destination")
|
||||
|
||||
self.entry = ttk.Entry(self, width=50)
|
||||
self.entry.grid(row=1, column=1, sticky="nsew")
|
||||
|
@ -160,32 +244,70 @@ class DestinationGame(ttk.LabelFrame):
|
|||
Select the source game
|
||||
:return:
|
||||
"""
|
||||
path = Path(tkinter.filedialog.asksaveasfilename(
|
||||
path = Path(tkinter.filedialog.askdirectory(
|
||||
title=_("SELECT_DESTINATION_GAME"),
|
||||
filetypes=[(_("WII GAMES"), "*.iso *.wbfs *.dol")],
|
||||
))
|
||||
|
||||
path.parent.mkdir(mode=0o777, parents=True, exist_ok=True)
|
||||
path.mkdir(mode=0o777, parents=True, exist_ok=True)
|
||||
|
||||
self.set_path(path)
|
||||
|
||||
def set_path(self, path: Path):
|
||||
if not os.access(path.parent, os.W_OK):
|
||||
if not os.access(path, os.W_OK):
|
||||
messagebox.showwarning(_("WARNING"), _("WARNING_DESTINATION_GAME_NOT_WRITABLE"))
|
||||
|
||||
self.entry.delete(0, tkinter.END)
|
||||
self.entry.insert(0, str(path.absolute()))
|
||||
|
||||
def get_path(self) -> Path:
|
||||
"""
|
||||
Get the destination game path
|
||||
:return: the game path
|
||||
"""
|
||||
path = Path(self.entry.get())
|
||||
if not path.exists(): raise DestinationGameError(path)
|
||||
return path
|
||||
|
||||
def set_state(self, state: InstallerState) -> None:
|
||||
"""
|
||||
Set the progress bar state when the installer change state
|
||||
:param state: state of the installer
|
||||
:return:
|
||||
"""
|
||||
for child in self.winfo_children():
|
||||
match state:
|
||||
case InstallerState.IDLE: child.config(state="normal")
|
||||
case InstallerState.INSTALLING: child.config(state="disabled")
|
||||
|
||||
|
||||
# Install button
|
||||
class ButtonInstall(ttk.Button):
|
||||
def __init__(self, master: tkinter.Tk):
|
||||
super().__init__(master, text="Install", command=self.install)
|
||||
|
||||
@threaded
|
||||
def install(self):
|
||||
# get space remaining on the C: drive
|
||||
if shutil.disk_usage(".").free < minimum_space_available:
|
||||
if not messagebox.askokcancel(_("WARNING"), _("WARNING_NOT_ENOUGH_SPACE_CONTINUE")): return
|
||||
try:
|
||||
self.master.set_state(InstallerState.INSTALLING)
|
||||
# get space remaining on the C: drive
|
||||
if shutil.disk_usage(".").free < minimum_space_available:
|
||||
if not messagebox.askokcancel(_("WARNING"), _("WARNING_NOT_ENOUGH_SPACE_CONTINUE")): return
|
||||
|
||||
game = Game(self.master.source_game.get_path())
|
||||
self.master.progress_function(game.install_mod())
|
||||
|
||||
finally:
|
||||
self.master.set_state(InstallerState.IDLE)
|
||||
|
||||
def set_state(self, state: InstallerState) -> None:
|
||||
"""
|
||||
Set the progress bar state when the installer change state
|
||||
:param state: state of the installer
|
||||
:return:
|
||||
"""
|
||||
match state:
|
||||
case InstallerState.IDLE: self.config(state="normal")
|
||||
case InstallerState.INSTALLING: self.config(state="disabled")
|
||||
|
||||
|
||||
# Progress bar
|
||||
|
@ -202,6 +324,24 @@ class ProgressBar(ttk.LabelFrame):
|
|||
self.description = ttk.Label(self, text="no process running", anchor="center", font=("TkDefaultFont", 10))
|
||||
self.description.grid(row=2, column=1, sticky="nsew")
|
||||
|
||||
def set_state(self, state: InstallerState) -> None:
|
||||
"""
|
||||
Set the progress bar state when the installer change state
|
||||
:param state: state of the installer
|
||||
:return:
|
||||
"""
|
||||
match state:
|
||||
case InstallerState.IDLE: self.grid_remove()
|
||||
case InstallerState.INSTALLING: self.grid()
|
||||
|
||||
def set_description(self, desc: str) -> None:
|
||||
"""
|
||||
Set the progress bar description
|
||||
:param desc: description
|
||||
:return:
|
||||
"""
|
||||
self.description.config(text=desc)
|
||||
|
||||
|
||||
# Combobox to select the pack
|
||||
class SelectPack(ttk.Combobox):
|
||||
|
@ -210,3 +350,13 @@ class SelectPack(ttk.Combobox):
|
|||
|
||||
for pack in Path("./Pack/").iterdir():
|
||||
self.insert(tkinter.END, pack.name)
|
||||
|
||||
def set_state(self, state: InstallerState) -> None:
|
||||
"""
|
||||
Set the progress bar state when the installer change state
|
||||
:param state: state of the installer
|
||||
:return:
|
||||
"""
|
||||
match state:
|
||||
case InstallerState.IDLE: self.config(state="readonly")
|
||||
case InstallerState.INSTALLING: self.config(state="disabled")
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import time
|
||||
from pathlib import Path
|
||||
from typing import Generator
|
||||
|
||||
from source.wt.wit import WITPath, Region
|
||||
|
||||
|
@ -27,3 +29,13 @@ class Game:
|
|||
:param dest: destination directory
|
||||
"""
|
||||
return self.wit_path.extract_all(dest)
|
||||
|
||||
def install_mod(self) -> Generator[str, None, None]:
|
||||
"""
|
||||
Patch the game with the mod
|
||||
"""
|
||||
i = 0
|
||||
while True:
|
||||
time.sleep(1)
|
||||
yield {"desc": f"step {i}"}
|
||||
i += 1
|
Loading…
Reference in a new issue