261 lines
8.1 KiB
Python
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))
|