mirror of
https://github.com/bottlesdevs/Bottles.git
synced 2026-10-28 14:29:36 +08:00
* refactor: migrate config from dict to object * fix: type hint import * fix: BottleConfig.load() * fix: config loading in BackupManager.import_backup() * fix: Remove unused import * fix: more guard code, type hints and Config object usage * fix: a potential crash when SteamManager.get_app_config() failed * Revert "fix: Remove unused import" This reverts commit2d48a4e08a. * add: new config field * fix: Config object value read * misc: remove review TODO comments * adapt to new BottleConfig object * fix RunAsync usage * add: BottleConfig._fix() * fix: type hint and Config object usage * fix: typo in FSR setting default value * adapt to new BottleConfig object * remove unnecessary DictCompat method * add: BottleConfig._filter() * fix: TemplateManager.new() * misc: critical guard code * fix: typo in commitb7ed61e678* misc: more type hinting * Revert "remove unnecessary DictCompat method" This reverts commit265633e733. * fix crash when user choose Cancel in ProtonAlertDialog * fix force stop all programs not work * adapt NVAPIComponent.check_bottle_nvngx to Config object * Better DictCompatMixIn * InstallerManager.__set_parameters incorrect adopt * fix typo incbe1dc760b* adopt LibraryManager.download_thumbnail to Config object * serialize Config object to yaml automatically * fix translation and remove workaround in0830fddbc9* fix missing locale for Chinese * comment * fix incorrect variable init position * remove 'in' extensions and some bugfix * fix executable filemode Co-authored-by: Mirko Brombin <send@mirko.pm>
197 lines
6.7 KiB
Python
197 lines
6.7 KiB
Python
# template.py
|
|
#
|
|
# Copyright 2022 brombinmirko <send@mirko.pm>
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, in version 3 of the License.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
#
|
|
|
|
import os
|
|
|
|
from bottles.backend.models.config import BottleConfig
|
|
from bottles.backend.utils import yaml
|
|
import uuid
|
|
import shutil
|
|
import contextlib
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
from bottles.backend.logger import Logger
|
|
from bottles.backend.utils.manager import ManagerUtils
|
|
from bottles.backend.globals import Paths
|
|
from bottles.backend.models.samples import Samples
|
|
|
|
logging = Logger()
|
|
|
|
|
|
class TemplateManager:
|
|
|
|
@staticmethod
|
|
def new(env: str, config: BottleConfig):
|
|
env = env.lower()
|
|
templates = TemplateManager.get_templates()
|
|
|
|
for template in templates:
|
|
if template["env"] == env:
|
|
logging.info(f"Caching new template for {env}…")
|
|
TemplateManager.delete_template(template["uuid"])
|
|
|
|
_uuid = str(uuid.uuid4())
|
|
logging.info(f"Creating new template: {_uuid}")
|
|
bottle = ManagerUtils.get_bottle_path(config)
|
|
|
|
delattr(config, "Name")
|
|
delattr(config, "Path")
|
|
delattr(config, "Creation_Date")
|
|
delattr(config, "Update_Date")
|
|
|
|
ignored = [
|
|
"dosdevices",
|
|
"states",
|
|
".fvs",
|
|
"*.yml"
|
|
".*"
|
|
]
|
|
|
|
_path = os.path.join(Paths.templates, _uuid)
|
|
logging.info("Copying files …")
|
|
|
|
with contextlib.suppress(FileNotFoundError):
|
|
shutil.copytree(bottle, _path, symlinks=True, ignore=shutil.ignore_patterns(*ignored))
|
|
|
|
template = {
|
|
"uuid": _uuid,
|
|
"env": env,
|
|
"created": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
"config": config
|
|
}
|
|
|
|
with open(os.path.join(_path, "template.yml"), "w") as f:
|
|
yaml.dump(template, f)
|
|
|
|
logging.info(f"New template {env} created", jn=True)
|
|
|
|
if not TemplateManager.__validate_template(_uuid):
|
|
logging.error("Template validation failed, will retry with next bottle.")
|
|
shutil.rmtree(_path)
|
|
|
|
@staticmethod
|
|
def __validate_template(template_uuid: str):
|
|
result = True
|
|
template_path = os.path.join(Paths.templates, template_uuid)
|
|
essentials = [
|
|
"drive_c/ProgramData",
|
|
"drive_c/windows",
|
|
]
|
|
|
|
if not os.path.exists(template_path):
|
|
logging.error(f"Template {template_uuid} not found!")
|
|
result = False
|
|
|
|
for essential in essentials:
|
|
if not os.path.exists(os.path.join(template_path, essential)):
|
|
logging.error(f"Template {template_uuid} is missing essential path: {essential}")
|
|
result = False
|
|
|
|
path_size = sum(file.stat().st_size for file in Path(template_path).rglob('*'))
|
|
if path_size < 300000000:
|
|
logging.error(f"Template {template_uuid} is too small!")
|
|
result = False
|
|
|
|
with open(os.path.join(template_path, "template.yml"), "r") as f:
|
|
template = yaml.load(f)
|
|
if template["uuid"] != template_uuid:
|
|
logging.error(f"Template {template_uuid} has invalid uuid!")
|
|
result = False
|
|
|
|
return result
|
|
|
|
@staticmethod
|
|
def get_template_manifest(template: str):
|
|
with open(os.path.join(Paths.templates, template, "template.yml"), "r") as f:
|
|
return yaml.load(f)
|
|
|
|
@staticmethod
|
|
def get_templates():
|
|
res = []
|
|
templates = os.listdir(Paths.templates)
|
|
|
|
for template in templates:
|
|
if os.path.exists(os.path.join(Paths.templates, template, "template.yml")):
|
|
_manifest = TemplateManager.get_template_manifest(template)
|
|
if _manifest is not None:
|
|
res.append(_manifest)
|
|
|
|
return res
|
|
|
|
@staticmethod
|
|
def delete_template(template_uuid: str):
|
|
if not template_uuid:
|
|
logging.error("Template uuid is not defined!")
|
|
return
|
|
|
|
if not os.path.exists(os.path.join(Paths.templates, template_uuid)):
|
|
logging.error(f"Template {template_uuid} not found!")
|
|
return
|
|
|
|
logging.info(f"Deleting template: {template_uuid}")
|
|
shutil.rmtree(os.path.join(Paths.templates, template_uuid))
|
|
logging.info("Template deleted successfully!")
|
|
|
|
@staticmethod
|
|
def check_outdated(template: dict):
|
|
env = template.get("env", "")
|
|
if env not in Samples.environments:
|
|
TemplateManager.delete_template(template.get("uuid"))
|
|
return True
|
|
|
|
_sample = Samples.environments[env]
|
|
for p in _sample.get("Parameters", {}):
|
|
_params = template.get("config", {}).get("Parameters", {})
|
|
if p not in _params or _params[p] != _sample["Parameters"][p]:
|
|
TemplateManager.delete_template(template.get("uuid"))
|
|
return True
|
|
|
|
for d in _sample.get("Installed_Dependencies", []):
|
|
_deps = template.get("config", {}).get("Installed_Dependencies", [])
|
|
if d not in _deps:
|
|
TemplateManager.delete_template(template.get("uuid"))
|
|
return True
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def get_env_template(env: str):
|
|
_templates = TemplateManager.get_templates()
|
|
for template in _templates:
|
|
if template["env"] == env.lower():
|
|
if TemplateManager.check_outdated(template):
|
|
logging.info(f"Deleting outdated template: {template['uuid']}")
|
|
return None
|
|
return template
|
|
return None
|
|
|
|
@staticmethod
|
|
def unpack_template(template: dict, config: BottleConfig):
|
|
def copy_func(source: str, dest: str):
|
|
if os.path.islink(source):
|
|
# we don't want symlinks from templates
|
|
return
|
|
shutil.copy2(source, dest)
|
|
|
|
logging.info(f"Unpacking template: {template['uuid']}")
|
|
bottle = ManagerUtils.get_bottle_path(config)
|
|
_path = os.path.join(Paths.templates, template['uuid'])
|
|
|
|
shutil.copytree(_path, bottle, symlinks=True, dirs_exist_ok=True, ignore=shutil.ignore_patterns('.*'), ignore_dangling_symlinks=True)
|
|
logging.info("Template unpacked successfully!")
|