WineBarrels-WineStarter/winestarter/instance.py

261 lines
8.1 KiB
Python

import abc
import asyncio
import os
import yaml
from . import paths, wine
# Step
class Step(abc.ABC):
@abc.abstractclassmethod
async def run(self, wine, instance):
raise NotImplementedError()
@abc.abstractclassmethod
async def content_to_hash(self):
raise NotImplementedError()
class _StepPreInit(Step):
async def run(self, wine, instance):
pass
async def content_to_hash(self):
return b""
class _StepWineInit(Step):
async def run(self, wine, isinstance):
return await wine.run_command([b"wineboot", b"--init"], with_display=False)
# TODO: gen volumes
async def content_to_hash(self):
version = await asyncio.create_subprocess_exec(["wine", "--version"], stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
result = version.communicate()
if result != 0:
raise RuntimeError("Can't get wine version.")
return version.stdout.splitlines()[0]
class _StepExec(Step):
__command: list
__in_drive_c: bool
__cwd_path: str
def __init__(self, command, in_drive_c: bool = False, cwd_path: str = None):
self.__command = list(map(str.encode, command))
self.__in_drive_c = in_drive_c
self.__cwd_path = cwd_path
async def run(self, wine, instance):
tmp_cwd = self.__cwd_path
if self.__in_drive_c:
tmp_cwd = None
return await wine.run_command(command=self.__command, cwd_in_instance=self.__in_drive_c, cwd_path=tmp_cwd)
async def content_to_hash(self):
return b"\x00".join(self.__command)
# Instance management
class Instance():
__instance: str
__run: _StepExec
__wine: wine.WineConfig
__steps: list
__volumes: dict
def __init__(self, instance: str, run: _StepExec, steps: list = None, volumes: dict = {}):
if not instance:
raise ValueError("instance name can't be empty")
self.__instance = str(instance)
self.__run = run
self.__wine = wine.WineConfig(os.path.join(
paths.INSTANCE_DIR, instance), os.path.join(paths.FAKEHOME_DIR, instance))
self.__steps = list(steps)
self.__volumes = dict(volumes)
async def __run_install(self, name: str, step: Step, last_hash: bytes):
if not isinstance(step, Step):
raise ValueError("step have to be a Step.")
# TODO: Check if allready done
# TODO: Update gui (show install window)
# TODO: Update gui (show that step is running)
result = step.run(self.__wine, self)
# TODO: Create backup
async def install(self):
# TODO: Set gui information
last_hash = await self.__run_install("", _StepPreInit(), b"")
last_hash = await self.__run_install("init", _StepWineInit(), last_hash)
for name, step in self.__steps:
last_hash = await self.__run_install(name, step, last_hash)
def get_instance(self):
return self.__instance
async def run(self):
await self.install()
# TODO: Update gui (hide main window)
await self.__run.run(self.__wine, self)
# Create instance
def _get_relative_file(current_file, file):
return os.path.abspath(os.path.join(os.path.split(current_file)[0], file))
def _gen_run(file, config):
config = dict(config)
# Load command
if "command" not in config:
raise ValueError(
"Can't find command in exec in file " + repr(file) + ".")
if not isinstance(config["command"], list):
raise ValueError(
"Command in exec is not an array in file " + repr(file) + ".")
command = config["command"]
del config["command"]
# Check if use in_drive_c
in_drive_c = False
if "in_drive_c" in config:
if config["in_drive_c"]:
in_drive_c = True
del config["in_drive_c"]
# Check for unused arguments
if config:
raise ValueError("Unused attributes " +
", ".join(config.keys()) + " in file " + repr(file) + ".")
# Return new exec step
return _StepExec(command=command, in_drive_c=in_drive_c)
def _load_volumes(file, config):
# Precheck
if not isinstance(config, list):
raise ValueError(
"Volume list have to be a list in file " + repr(file) + ".")
# Volumes
volumes = []
for i in config:
# Check entry
if not isinstance(i, dict):
raise ValueError(
"Volume instance have to be an dict in file " + repr(file) + ".")
i = dict(i)
# Load name and path
if "name" not in i:
raise ValueError(
"Can't find name in volume info in file " + repr(file) + ".")
if "path" not in i:
raise ValueError(
"Can't find path in volume info in file " + repr(file) + ".")
name = str(i["name"])
path = str(i["path"])
del i["name"]
del i["path"]
volumes.append((name, path))
# Check for unused attributes
if i:
raise ValueError("Volume has unused attirbutes " +
", ".join(i.keys()) + " in file " + repr(file) + ".")
return volumes
def _load_module(file: str, content: dict):
# Check if string to load file
if isinstance(content, str):
file = _get_relative_file(file, content)
with open(file, "rb") as f:
content = yaml.load(f, Loader=yaml.SafeLoader)
content = dict(content)
# Load sub modules
result = []
volumes = []
if "modules" in content:
modules = content["modules"]
del content["modules"]
if not isinstance(modules, list):
raise ValueError(
"\"modules\" isn't a list in file " + repr(modules) + ".")
for i in modules:
tmp_result, tmp_volumes = _load_module(file, i)
result += tmp_result
volumes += tmp_volumes
# Load volumes
if "volumes" in content:
volumes += _load_volumes(file, content["volumes"])
del content["volumes"]
# Load name
if "name" not in content:
raise ValueError(
"name is missing in module in file " + repr(file) + ".")
name = content["name"]
del content["name"]
# Check if type exists
if "type" not in content:
raise ValueError("Can't find type in file " + repr(file) + ".")
type_name = content["type"]
del content["type"]
if type_name == "exec":
if tuple(content.keys()) != ("run",):
raise ValueError("Exec only supports \"run\" attribute but has " +
", ".join(content.keys()) + " in file " + repr(file) + ".")
result.append((name, _gen_run(file, content["run"])))
else:
raise ValueError("Type " + repr(type_name) +
" is unknown in file " + repr(file) + ".")
# Cleanup modules
return result, volumes
def gen_instance(file):
# Load base data
with open(file, "rb") as f:
content = dict(yaml.load(f, Loader=yaml.SafeLoader))
if "name" not in content:
raise ValueError("Can't find name inside of " + repr(file) + ".")
name = content["name"]
del content["name"]
# Load run
if "run" not in content:
raise ValueError("Can't find run inside of " + repr(file) + ".")
run = _gen_run(file, content["run"])
del content["run"]
# Load modules
modules = []
volumes = []
if "modules" not in content:
raise ValueError("Can't find \"modules\" in " + repr(file) + ".")
if not isinstance(content["modules"], list):
raise ValueError(
"\"modules\" have to be an array in file " + repr(file) + ".")
for i in content["modules"]:
tmp_modules, tmp_volumes = _load_module(file, i)
modules += tmp_modules
volumes += tmp_volumes
del content["modules"]
# Check unused attributes
if content:
raise ValueError("Unused attributes " +
", ".join(content.keys()) + " in file " + repr(file) + ".")
# Return new instance
return Instance(instance=name, run=run, steps=modules, volumes=dict(volumes))