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:
Faraphel 2022-06-10 21:40:03 +02:00
parent f9db2e96ea
commit 753be7df0d
3 changed files with 190 additions and 10 deletions

View file

@ -1,3 +1,7 @@
from threading import Thread
from typing import Callable
__version__ = (0, 12, 0) __version__ = (0, 12, 0)
__author__ = 'Faraphel' __author__ = 'Faraphel'
@ -10,3 +14,17 @@ Mo = 1_000 * Ko
Go = 1_000 * Mo Go = 1_000 * Mo
minimum_space_available = 15*Go 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

View file

@ -1,3 +1,4 @@
import enum
import shutil import shutil
import tkinter import tkinter
from pathlib import Path from pathlib import Path
@ -6,13 +7,30 @@ from tkinter import ttk
from tkinter import filedialog from tkinter import filedialog
from tkinter import messagebox from tkinter import messagebox
import webbrowser import webbrowser
from typing import Generator
from source.mkw.Game import Game
from source.translation import translate as _ from source.translation import translate as _
from source import event from source import event
from source import * from source import *
import os 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 # Main window for the installer
class Window(tkinter.Tk): class Window(tkinter.Tk):
def __init__(self): def __init__(self):
@ -42,6 +60,8 @@ class Window(tkinter.Tk):
self.progress_bar = ProgressBar(self) self.progress_bar = ProgressBar(self)
self.progress_bar.grid(row=5, column=1, sticky="nsew") self.progress_bar.grid(row=5, column=1, sticky="nsew")
self.set_state(InstallerState.IDLE)
def run(self) -> None: def run(self) -> None:
""" """
Run the installer Run the installer
@ -58,6 +78,24 @@ class Window(tkinter.Tk):
""" """
return None 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 # Menu bar
class Menu(tkinter.Menu): class Menu(tkinter.Menu):
@ -101,14 +139,39 @@ class Menu(tkinter.Menu):
super().__init__(master, tearoff=False) super().__init__(master, tearoff=False)
master.add_cascade(label="Help", menu=self) 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="Discord", command=lambda: webbrowser.open(discord_url))
self.add_command(label="Github Wiki", command=lambda: webbrowser.open(github_wiki_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 # Select game frame
class SourceGame(ttk.LabelFrame): class SourceGame(ttk.LabelFrame):
def __init__(self, master: tkinter.Tk): 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 = ttk.Entry(self, width=50)
self.entry.grid(row=1, column=1, sticky="nsew") 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.delete(0, tkinter.END)
self.entry.insert(0, str(path.absolute())) 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 # Select game destination frame
class DestinationGame(ttk.LabelFrame): class DestinationGame(ttk.LabelFrame):
def __init__(self, master: tkinter.Tk): 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 = ttk.Entry(self, width=50)
self.entry.grid(row=1, column=1, sticky="nsew") self.entry.grid(row=1, column=1, sticky="nsew")
@ -160,33 +244,71 @@ class DestinationGame(ttk.LabelFrame):
Select the source game Select the source game
:return: :return:
""" """
path = Path(tkinter.filedialog.asksaveasfilename( path = Path(tkinter.filedialog.askdirectory(
title=_("SELECT_DESTINATION_GAME"), 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) self.set_path(path)
def set_path(self, 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")) messagebox.showwarning(_("WARNING"), _("WARNING_DESTINATION_GAME_NOT_WRITABLE"))
self.entry.delete(0, tkinter.END) self.entry.delete(0, tkinter.END)
self.entry.insert(0, str(path.absolute())) 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 # Install button
class ButtonInstall(ttk.Button): class ButtonInstall(ttk.Button):
def __init__(self, master: tkinter.Tk): def __init__(self, master: tkinter.Tk):
super().__init__(master, text="Install", command=self.install) super().__init__(master, text="Install", command=self.install)
@threaded
def install(self): def install(self):
try:
self.master.set_state(InstallerState.INSTALLING)
# get space remaining on the C: drive # get space remaining on the C: drive
if shutil.disk_usage(".").free < minimum_space_available: if shutil.disk_usage(".").free < minimum_space_available:
if not messagebox.askokcancel(_("WARNING"), _("WARNING_NOT_ENOUGH_SPACE_CONTINUE")): return 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 # Progress bar
class ProgressBar(ttk.LabelFrame): class ProgressBar(ttk.LabelFrame):
@ -202,6 +324,24 @@ class ProgressBar(ttk.LabelFrame):
self.description = ttk.Label(self, text="no process running", anchor="center", font=("TkDefaultFont", 10)) self.description = ttk.Label(self, text="no process running", anchor="center", font=("TkDefaultFont", 10))
self.description.grid(row=2, column=1, sticky="nsew") 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 # Combobox to select the pack
class SelectPack(ttk.Combobox): class SelectPack(ttk.Combobox):
@ -210,3 +350,13 @@ class SelectPack(ttk.Combobox):
for pack in Path("./Pack/").iterdir(): for pack in Path("./Pack/").iterdir():
self.insert(tkinter.END, pack.name) 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")

View file

@ -1,4 +1,6 @@
import time
from pathlib import Path from pathlib import Path
from typing import Generator
from source.wt.wit import WITPath, Region from source.wt.wit import WITPath, Region
@ -27,3 +29,13 @@ class Game:
:param dest: destination directory :param dest: destination directory
""" """
return self.wit_path.extract_all(dest) 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