mirror of
https://github.com/FAUSheppy/homelab_gamevault
synced 2025-12-06 06:51:36 +01:00
wip: implement dependencies & install
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
*.zip
|
*.zip
|
||||||
*.swp
|
*.swp
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
cache/
|
||||||
|
install/
|
||||||
13
adminrun.py
Normal file
13
adminrun.py
Normal 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)
|
||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
8
example_software_root/directx9/meta.yaml
Normal file
8
example_software_root/directx9/meta.yaml
Normal 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
|
||||||
@@ -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'''
|
||||||
|
|||||||
74
software.py
74
software.py
@@ -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))
|
||||||
Reference in New Issue
Block a user