wip: implement dependencies & install

This commit is contained in:
Yannik Schmidt
2024-02-25 01:05:53 +01:00
parent d9d63bfe01
commit 2f3050df47
9 changed files with 171 additions and 61 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
*.zip *.zip
*.swp *.swp
__pycache__/ __pycache__/
cache/
install/

13
adminrun.py Normal file
View File

@@ -0,0 +1,13 @@
from pyuac.main_decorator import main_requires_admin
import sys
import subprocess
@main_requires_admin(return_output=True)
def main(path):
p = subprocess.Popen(path, subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
stdout, stderr = p.communicate()
if __name__ == '__main__':
path = sys.argv[-1]
rv = main(path)
print(rv)

View File

@@ -45,7 +45,6 @@ def load_main():
# create tiles from meta files # # create tiles from meta files #
for software in db.find_all_metadata(): for software in db.find_all_metadata():
print(software.title)
create_main_window_tile(software) create_main_window_tile(software)
# set update listener & update positions # # set update listener & update positions #
@@ -68,10 +67,13 @@ def load_details(app, software):
def create_main_window_tile(software): def create_main_window_tile(software):
'''Create the main window tile''' '''Create the main window tile'''
if software.get_thumbnail():
img = PIL.Image.open(software.get_thumbnail()) img = PIL.Image.open(software.get_thumbnail())
img = img.resize((200, 300)) img = img.resize((200, 300))
img = PIL.ImageTk.PhotoImage(img) else:
img = PIL.Image.new('RGB', (200, 300))
img = PIL.ImageTk.PhotoImage(img)
button = customtkinter.CTkButton(app, image=img, button = customtkinter.CTkButton(app, image=img,
width=200, height=300, width=200, height=300,
command=lambda: switch_to_game_details(software), command=lambda: switch_to_game_details(software),
@@ -117,7 +119,7 @@ def update_button_positions(event=None):
if __name__ == "__main__": if __name__ == "__main__":
# define data backend # # define data backend #
db = data_backend.LocalFS(None, None, "./cache", remote_root_dir="example_software_root") db = data_backend.LocalFS(None, None, "./install/", remote_root_dir="example_software_root")
load_main() load_main()

View File

@@ -11,8 +11,12 @@ def create_details_page(app, software):
elements = [] elements = []
if software.get_thumbnail():
img = PIL.Image.open(software.get_thumbnail()) img = PIL.Image.open(software.get_thumbnail())
img = img.resize((200, 300)) img = img.resize((200, 300))
else:
img = PIL.Image.new('RGB', (200, 300))
img = PIL.ImageTk.PhotoImage(img) img = PIL.ImageTk.PhotoImage(img)
# thumbnail image # # thumbnail image #
@@ -48,6 +52,7 @@ def create_details_page(app, software):
elements.append(description) elements.append(description)
# dependencies # # dependencies #
if software.dependencies:
dependencies_text = ",".join(software.dependencies) dependencies_text = ",".join(software.dependencies)
dependencies = customtkinter.CTkLabel(info_frame, text=dependencies_text) dependencies = customtkinter.CTkLabel(info_frame, text=dependencies_text)
dependencies.pack(anchor="w", side="top", padx=20) dependencies.pack(anchor="w", side="top", padx=20)
@@ -55,19 +60,24 @@ def create_details_page(app, software):
# buttons # # buttons #
install_button = customtkinter.CTkButton(info_frame, text="Install", install_button = customtkinter.CTkButton(info_frame, text="Install",
command=lambda: software.install(software)) command=lambda: software.install())
remove_button = customtkinter.CTkButton(info_frame, text="Remove", remove_button = customtkinter.CTkButton(info_frame, text="Remove",
command=lambda: software.remove(software)) command=lambda: software.remove())
if software.run_exe:
run_button = customtkinter.CTkButton(info_frame, text="Run",
command=lambda: software.run())
run_button.pack(padx=10, pady=30, anchor="sw", side="left")
elements.append(run_button)
install_button.pack(padx=20, pady=30, anchor="sw", side="left") install_button.pack(padx=10, pady=30, anchor="sw", side="left")
remove_button.pack(padx=20, pady=30, anchor="sw", side="left") remove_button.pack(padx=10, pady=30, anchor="sw", side="left")
elements.append(install_button) elements.append(install_button)
elements.append(remove_button) elements.append(remove_button)
# add other pictures # # add other pictures #
i = 0 i = 0
for path in software.pictures[1:]: for path in software.pictures[1:]:
img = PIL.Image.open(software.get_thumbnail()) img = PIL.Image.open(path)
img = img.resize((200, 300)) img = img.resize((200, 300))
img = PIL.ImageTk.PhotoImage(img) img = PIL.ImageTk.PhotoImage(img)
extra_pic_button = customtkinter.CTkButton(app, text="", image=img, width=200, height=300, extra_pic_button = customtkinter.CTkButton(app, text="", image=img, width=200, height=300,

View File

@@ -5,17 +5,17 @@ import software
class DataBackend: class DataBackend:
def __init__(self, user, password, cache_dir, remote_root_dir=None): def _create_cache_dir(self, cache_dir):
os.makedirs(cache_dir, exist_ok=True)
def __init__(self, user, password, install_dir, remote_root_dir=None):
self.user = user self.user = user
self.password = password self.password = password
self.cache_dir = cache_dir
self.remote_root_dir = remote_root_dir self.remote_root_dir = remote_root_dir
self.install_dir = install_dir
if not os.path.isdir(self.cache_dir): def get(self, path, return_content=False):
os.mkdir(self.cache_dir)
def get(self, path):
'''Return the contents of this path''' '''Return the contents of this path'''
raise NotImplementedError() raise NotImplementedError()
@@ -29,18 +29,43 @@ class DataBackend:
class LocalFS(DataBackend): class LocalFS(DataBackend):
def get(self, path): def get(self, path, cache_dir=None, return_content=False):
# check the load cache dir #
if cache_dir:
self._create_cache_dir(cache_dir)
elif not cache_dir and not return_content:
AssertionError("Need to set either cache_dir or return_content!")
# prepend root dir if not given #
fullpath = path
if self.remote_root_dir and not path.startswith(self.remote_root_dir):
fullpath = os.path.join(self.remote_root_dir, path) fullpath = os.path.join(self.remote_root_dir, path)
# load the file on remote #
with open(fullpath, "rb") as f: with open(fullpath, "rb") as f:
target = os.path.join(self.cache_dir, os.path.basename(path)) print(cache_dir, path)
target = os.path.join(cache_dir, os.path.basename(path))
with open(target, "wb") as ft: with open(target, "wb") as ft:
if return_content:
return f.read()
ft.write(f.read()) ft.write(f.read())
return target return target
def list(self, path): def list(self, path, fullpaths=False):
# prepend root dir if not given #
fullpath = path
if self.remote_root_dir and not path.startswith(self.remote_root_dir):
fullpath = os.path.join(self.remote_root_dir, path) fullpath = os.path.join(self.remote_root_dir, path)
if not os.path.isdir(fullpath):
return []
if fullpaths:
return [ os.path.join(path, filename) for filename in os.listdir(fullpath)]
else:
return os.listdir(fullpath) return os.listdir(fullpath)
def find_all_metadata(self): def find_all_metadata(self):
@@ -51,6 +76,6 @@ class LocalFS(DataBackend):
if not os.path.isfile(meta_file): if not os.path.isfile(meta_file):
continue continue
else: else:
meta_info_list.append(software.Software(meta_file)) meta_info_list.append(software.Software(meta_file, self))
return meta_info_list return meta_info_list

View File

@@ -8,8 +8,9 @@ description: >
to a town that worships ducks. Dink is never freed from the grievances of being a pig farmer, to a town that worships ducks. Dink is never freed from the grievances of being a pig farmer,
a fact he is far too often reminded of by his nemesis, Milder Flatstomp. a fact he is far too often reminded of by his nemesis, Milder Flatstomp.
dependencies: dependencies:
- dummy_dep_1 - DirectX 9.0c
link_only: false link_only: false
extra_files: extra_files:
dummy_file_1.txt: "%APP_DATA%/game_vault/" dummy_file_1.txt: "%APP_DATA%/game_vault/"
dummy_dir_1: "%APP_DATA%/game_vault/" dummy_dir_1: "%APP_DATA%/game_vault/"
run_exe: "GNUFreeDink/dink.exe"

View File

@@ -0,0 +1,8 @@
genre: System Tools
title: DirectX 9.0c
description: >
The last official DirectX9 driver, needed for various games.
dependencies:
link_only: false
extra_files:
installer: DX/DXSETUP.exe

View File

@@ -27,9 +27,22 @@ def install_extra_files(extra_files_list, path):
'''Copy/Install extra gamedata to a give location''' '''Copy/Install extra gamedata to a give location'''
pass pass
def launch_software(path, synchronous=False): def run_exe(path, synchronous=False):
'''Launches a given software''' '''Launches a given software'''
pass
if synchronous:
raise NotImplementedError("SYNC not yet implemented")
print("Executing:", path)
try:
subprocess.Popen(path)
except OSError as e:
if "WinError 740" in str(e):
p = subprocess.Popen(["python", "adminrun.py", path],
subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
print(p.communicate())
else:
raise e
def remove_software(path): def remove_software(path):
'''Remove a software at the target location''' '''Remove a software at the target location'''

View File

@@ -1,25 +1,23 @@
import yaml import yaml
import os import os
import localaction import localaction
import zipfile
class Software: class Software:
def __init__(self, directory, backend): def __init__(self, meta_file, backend):
if os.path.isfile(directory) and directory.endswith("meta.yaml"): self.meta_file = meta_file
directory = os.path.dirname(directory) self.directory = os.path.dirname(meta_file)
self.directory = directory
self._load_from_yaml()
self.backend = backend self.backend = backend
self.cache_dir = os.path.join("cache", self.directory)
self._load_from_yaml()
def _load_from_yaml(self): def _load_from_yaml(self):
fullpath = os.path.join(self.directory, "meta.yaml") content = self.backend.get(self.meta_file, self.cache_dir, return_content=True)
self.info_file = fullpath
with open(fullpath) as f:
meta = yaml.safe_load(f)
meta = yaml.safe_load(content)
self.title = meta.get("title") self.title = meta.get("title")
self.genre = meta.get("genre") self.genre = meta.get("genre")
self.description = meta.get("description") self.description = meta.get("description")
@@ -27,30 +25,68 @@ class Software:
self.link_only = meta.get("link_only") self.link_only = meta.get("link_only")
self.link = meta.get("link") self.link = meta.get("link")
self.extra_files = meta.get("extra_files") self.extra_files = meta.get("extra_files")
self.run_exe = meta.get("run_exe")
self.installer = meta.get("installer")
self.pictures = [ self.backend.get(pp, self.cache_dir) for pp in
self.backend.list(os.path.join(self.directory, "pictures"), fullpaths=True) ]
self.reg_files = self.backend.list(os.path.join(self.directory, "registry_files"), fullpaths=True)
self.pictures = [os.path.join(self.directory, "pictures", p) for p in
os.listdir(os.path.join(self.directory, "pictures"))]
def get_thumbnail(self): def get_thumbnail(self):
'''Return the thumbnail for this software'''
if not self.pictures:
return None
return self.pictures[0] return self.pictures[0]
def _extract_to_target(self, cache_src, target): def _extract_to_target(self, cache_src, target):
'''Extract a cached, downloaded zip to the target location''' '''Extract a cached, downloaded zip to the target location'''
software_path = os.path.join(target, self.title)
os.makedirs(software_path, exist_ok=True)
with zipfile.ZipFile(cache_src, 'r') as zip_ref:
zip_ref.extractall(software_path)
def install(self): def install(self):
'''Install this software from the backend''' '''Install this software from the backend'''
local_file = self.backend.get_exe_or_data(): print("Installing:", self.title, self.directory)
path = os.path.join(self.directory, "main_dir")
remote_file = self.backend.list(path, fullpaths=True)[0]
local_file = self.backend.get(remote_file, self.cache_dir)
# execute or unpack #
if local_file.endswith(".exe"): if local_file.endswith(".exe"):
localaction.run_exe(local_file) localaction.run_exe(local_file)
elif local_file.endswith(".zip"): elif local_file.endswith(".zip"):
_extract_to_target(INSTALL_DIR) self._extract_to_target(local_file, self.backend.install_dir)
# download registry # download & install registry files #
# install registry for rf in self.reg_files:
# TODO dependencies # path = self.backend.get(rf, cache_dir=self.cache_dir)
# download gamefiles localaction.install_registry_file(path)
# install gamefiles
# install dependencies #
if self.dependencies:
avail_software = self.backend.find_all_metadata()
for s in avail_software:
if s.title in self.dependencies:
s.install()
# run installer if set #
if self.installer:
installer_path = os.path.join(self.backend.install_dir, self.title, self.installer)
print("Running installer:", installer_path)
localaction.run_exe(installer_path)
# TODO download & install gamefiles #
def run(self):
'''Run the configured exe for this software'''
if self.run_exe:
localaction.run_exe(os.path.join(self.backend.install_dir, self.title, self.run_exe))