mirror of
https://github.com/Faraphel/Atlas-Install.git
synced 2025-07-03 11:18:26 +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)
|
__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
|
||||||
|
|
|
@ -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,32 +244,70 @@ 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):
|
||||||
# get space remaining on the C: drive
|
try:
|
||||||
if shutil.disk_usage(".").free < minimum_space_available:
|
self.master.set_state(InstallerState.INSTALLING)
|
||||||
if not messagebox.askokcancel(_("WARNING"), _("WARNING_NOT_ENOUGH_SPACE_CONTINUE")): return
|
# 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
|
# 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 = 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")
|
||||||
|
|
|
@ -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
|
Loading…
Reference in a new issue