mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
bootstrapping
This commit is contained in:
parent
be308a48ba
commit
01b3b96983
8 changed files with 197 additions and 25 deletions
|
|
@ -9,6 +9,7 @@ import tempfile
|
|||
from typing import Union, Callable, Dict
|
||||
from zipfile import ZipFile
|
||||
from pathlib import Path
|
||||
from speedcopy import copyfile
|
||||
|
||||
from appdirs import user_data_dir
|
||||
from pype.version import __version__
|
||||
|
|
@ -85,6 +86,11 @@ class BootstrapRepos:
|
|||
def install_live_repos(self, repo_dir: Path = None) -> Union[Path, None]:
|
||||
"""Copy zip created from Pype repositories to user data dir.
|
||||
|
||||
This detect Pype version either in local "live" Pype repository
|
||||
or in user provided path. Then it will zip in in temporary directory
|
||||
and finally it will move it to destination which is user data
|
||||
directory. Existing files will be replaced.
|
||||
|
||||
Args:
|
||||
repo_dir (Path, optional): Path to Pype repository.
|
||||
|
||||
|
|
@ -92,6 +98,9 @@ class BootstrapRepos:
|
|||
Path: path of installed repository file.
|
||||
|
||||
"""
|
||||
# if repo dir is not set, we detect local "live" Pype repository
|
||||
# version and use it as a source. Otherwise repo_dir is user
|
||||
# entered location.
|
||||
if not repo_dir:
|
||||
version = self.get_local_version()
|
||||
repo_dir = self.live_repo_dir
|
||||
|
|
@ -99,10 +108,10 @@ class BootstrapRepos:
|
|||
version = self.get_version(repo_dir)
|
||||
|
||||
# create destination directory
|
||||
try:
|
||||
os.makedirs(self.data_dir)
|
||||
except OSError:
|
||||
self._log.error("directory already exists")
|
||||
if not self.data_dir.exists():
|
||||
self.data_dir.mkdir(parents=True)
|
||||
|
||||
# create zip inside temporary directory.
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
temp_zip = \
|
||||
Path(temp_dir) / f"pype-repositories-v{version}.zip"
|
||||
|
|
@ -214,7 +223,8 @@ class BootstrapRepos:
|
|||
|
||||
os.environ["PYTHONPATH"] = os.pathsep.join(paths)
|
||||
|
||||
def find_pype(self) -> Union[Dict[str, Path], None]:
|
||||
def find_pype(
|
||||
self, pype_path: Path = None) -> Union[Dict[str, Path], None]:
|
||||
"""Get ordered dict of detected Pype version.
|
||||
|
||||
Resolution order for Pype is following:
|
||||
|
|
@ -223,6 +233,9 @@ class BootstrapRepos:
|
|||
2) We try to find ``pypePath`` in registry setting
|
||||
3) We use user data directory
|
||||
|
||||
Args:
|
||||
pype_path (Path, optional): Try to find Pype on the given path.
|
||||
|
||||
Returns:
|
||||
dict of Path: Dictionary of detected Pype version.
|
||||
Key is version, value is path to zip file.
|
||||
|
|
@ -244,6 +257,10 @@ class BootstrapRepos:
|
|||
# nothing found in registry, we'll use data dir
|
||||
pass
|
||||
|
||||
# if we have pyp_path specified, search only there.
|
||||
if pype_path:
|
||||
dir_to_search = pype_path
|
||||
|
||||
# pype installation dir doesn't exists
|
||||
if not dir_to_search.exists():
|
||||
return None
|
||||
|
|
@ -275,7 +292,7 @@ class BootstrapRepos:
|
|||
None: if not.
|
||||
|
||||
"""
|
||||
os.environ["AVALON_MONGO"] = mongo_url
|
||||
os.environ["PYPE_MONGO"] = mongo_url
|
||||
env = load_environments()
|
||||
if not env.get("PYPE_PATH"):
|
||||
return None
|
||||
|
|
@ -299,19 +316,59 @@ class BootstrapRepos:
|
|||
|
||||
"""
|
||||
pype_path = None
|
||||
# try to get pype path from mongo.
|
||||
if location.startswith("mongodb"):
|
||||
pype_path = self._get_pype_from_mongo(location)
|
||||
if not pype_path:
|
||||
self._log.error("cannot find PYPE_PATH in settings.")
|
||||
return None
|
||||
|
||||
# if not successful, consider location to be fs path.
|
||||
if not pype_path:
|
||||
pype_path = Path(location)
|
||||
|
||||
# test if this path does exist.
|
||||
if not pype_path.exists():
|
||||
self._log.error(f"{pype_path} doesn't exists.")
|
||||
return None
|
||||
|
||||
# find pype zip files in location. In that location, there can be
|
||||
# either "live" Pype repository, or multiple zip files.
|
||||
versions = self.find_pype(pype_path)
|
||||
if versions:
|
||||
latest_version = (
|
||||
list(versions.keys())[-1], list(versions.values())[-1])
|
||||
self._log.info(f"found Pype zips in [ {pype_path} ].")
|
||||
self._log.info(f"latest version found is [ {latest_version[0]} ]")
|
||||
|
||||
destination = self.data_dir / latest_version[1].name
|
||||
|
||||
# test if destination file already exist, if so lets delete it.
|
||||
# we consider path on location as authoritative place.
|
||||
if destination.exists():
|
||||
try:
|
||||
destination.unlink()
|
||||
except OSError:
|
||||
self._log.error(
|
||||
f"cannot remove already existing {destination}",
|
||||
exc_info=True)
|
||||
return None
|
||||
|
||||
# create destination parent directories even if they don't exist.
|
||||
if not destination.parent.exists():
|
||||
destination.parent.mkdir(parents=True)
|
||||
|
||||
try:
|
||||
copyfile(latest_version[1].as_posix(), destination.as_posix())
|
||||
except OSError:
|
||||
self._log.error(
|
||||
"cannot copy detected version to user data directory",
|
||||
exc_info=True)
|
||||
return None
|
||||
return destination
|
||||
|
||||
# if we got here, it means that location is "live" Pype repository.
|
||||
# we'll create zip from it and move it to user data dir.
|
||||
repo_file = self.install_live_repos(pype_path)
|
||||
if not repo_file.exists():
|
||||
self._log.error(f"installing zip {repo_file} failed.")
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from Qt import QtCore, QtGui, QtWidgets
|
|||
from Qt.QtGui import QValidator
|
||||
|
||||
from .install_thread import InstallThread
|
||||
from .tools import validate_path_string
|
||||
from .tools import validate_path_string, validate_mongo_connection
|
||||
|
||||
|
||||
class InstallDialog(QtWidgets.QDialog):
|
||||
|
|
@ -152,8 +152,27 @@ class InstallDialog(QtWidgets.QDialog):
|
|||
def get_mongo_url(self):
|
||||
return self._mongo_url
|
||||
|
||||
def set_valid(self):
|
||||
self._mongo_input.setStyleSheet(
|
||||
"""
|
||||
background-color: rgb(19, 19, 19);
|
||||
color: rgb(64, 230, 132);
|
||||
padding: 0.5em;
|
||||
border: 1px solid rgb(32, 64, 32);
|
||||
"""
|
||||
)
|
||||
|
||||
def set_invalid(self):
|
||||
self._mongo_input.setStyleSheet(
|
||||
"""
|
||||
background-color: rgb(32, 19, 19);
|
||||
color: rgb(255, 69, 0);
|
||||
padding: 0.5em;
|
||||
border: 1px solid rgb(32, 64, 32);
|
||||
"""
|
||||
)
|
||||
|
||||
self._mongo = MongoWidget(self)
|
||||
self._mongo.hide()
|
||||
|
||||
# Bottom button bar
|
||||
# --------------------------------------------------------------------
|
||||
|
|
@ -321,10 +340,19 @@ class InstallDialog(QtWidgets.QDialog):
|
|||
border: 1px solid rgb(32, 64, 32);
|
||||
"""
|
||||
)
|
||||
if not self._path or not self._path.startswith("mongodb"):
|
||||
valid, reason = validate_mongo_connection(
|
||||
self._mongo.get_mongo_url()
|
||||
)
|
||||
if not valid:
|
||||
self._mongo.set_invalid()
|
||||
self._update_console(f"!!! {reason}", True)
|
||||
return
|
||||
else:
|
||||
self._mongo.set_valid()
|
||||
|
||||
self._disable_buttons()
|
||||
self._install_thread = InstallThread(self)
|
||||
self._install_thread.ask_for_mongo.connect(self._show_mongo)
|
||||
self._install_thread.message.connect(self._update_console)
|
||||
self._install_thread.progress.connect(self._update_progress)
|
||||
self._install_thread.finished.connect(self._enable_buttons)
|
||||
|
|
@ -338,9 +366,6 @@ class InstallDialog(QtWidgets.QDialog):
|
|||
def _on_exit_clicked(self):
|
||||
self.close()
|
||||
|
||||
def _show_mongo(self):
|
||||
self._update_console("mongo showed")
|
||||
|
||||
def _path_changed(self, path: str) -> str:
|
||||
"""Set path."""
|
||||
self._path = path
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@
|
|||
"""Working thread for installer."""
|
||||
import os
|
||||
from Qt.QtCore import QThread, Signal
|
||||
from igniter.tools import load_environments
|
||||
|
||||
from speedcopy import copyfile
|
||||
|
||||
from .bootstrap_repos import BootstrapRepos
|
||||
from .tools import validate_mongo_connection
|
||||
|
|
@ -33,7 +34,15 @@ class InstallThread(QThread):
|
|||
QThread.__init__(self, parent)
|
||||
|
||||
def run(self):
|
||||
"""Thread entry point.
|
||||
|
||||
Using :class:`BootstrapRepos` to either install Pype as zip files
|
||||
or copy them from location specified by user or retrieved from
|
||||
database.
|
||||
|
||||
"""
|
||||
self.message.emit("Installing Pype ...", False)
|
||||
|
||||
# find local version of Pype
|
||||
bs = BootstrapRepos(progress_callback=self.set_progress)
|
||||
local_version = bs.get_local_version()
|
||||
|
|
@ -42,7 +51,24 @@ class InstallThread(QThread):
|
|||
# zip content of `repos`, copy it to user data dir and append
|
||||
# version to it.
|
||||
if not self._path:
|
||||
self.ask_for_mongo.emit(True)
|
||||
# user did not entered url
|
||||
if not self._mongo:
|
||||
# it not set in environment
|
||||
if not os.getenv("PYPE_MONGO"):
|
||||
# try to get it from settings registry
|
||||
try:
|
||||
self._mongo = bs.registry.get_secure_item("pypeMongo")
|
||||
except ValueError:
|
||||
self.message.emit(
|
||||
"!!! We need MongoDB URL to proceed.", True)
|
||||
return
|
||||
else:
|
||||
self._mongo = os.getenv("PYPE_MONGO")
|
||||
else:
|
||||
bs.registry.set_secure_item("pypeMongo", self._mongo)
|
||||
|
||||
os.environ["PYPE_MONGO"] = self._mongo
|
||||
|
||||
self.message.emit(
|
||||
f"We will use local Pype version {local_version}", False)
|
||||
repo_file = bs.install_live_repos()
|
||||
|
|
@ -52,13 +78,15 @@ class InstallThread(QThread):
|
|||
return
|
||||
self.message.emit(f"installed as {repo_file}", False)
|
||||
else:
|
||||
# if we have mongo connection string, validate it, set it to
|
||||
# user settings and get PYPE_PATH from there.
|
||||
if self._mongo:
|
||||
if not validate_mongo_connection(self._mongo):
|
||||
self.message.emit(
|
||||
f"!!! invalid mongo url {self._mongo}", True)
|
||||
return
|
||||
bs.registry.set_secure_item("avalonMongo", self._mongo)
|
||||
os.environ["AVALON_MONGO"] = self._mongo
|
||||
bs.registry.set_secure_item("pypeMongo", self._mongo)
|
||||
os.environ["PYPE_MONGO"] = self._mongo
|
||||
|
||||
repo_file = bs.process_entered_location(self._path)
|
||||
|
||||
|
|
@ -72,5 +100,5 @@ class InstallThread(QThread):
|
|||
def set_mongo(self, mongo: str) -> None:
|
||||
self._mongo = mongo
|
||||
|
||||
def set_progress(self, progress: int):
|
||||
def set_progress(self, progress: int) -> None:
|
||||
self.progress.emit(progress)
|
||||
|
|
|
|||
|
|
@ -61,13 +61,15 @@ def validate_path_string(path: str) -> (bool, str):
|
|||
the reason why it failed.
|
||||
|
||||
"""
|
||||
if not path:
|
||||
return True, "Empty string"
|
||||
parsed = urlparse(path)
|
||||
if parsed.scheme == "mongodb":
|
||||
return validate_mongo_connection(path)
|
||||
# test for uuid
|
||||
try:
|
||||
uuid.UUID(path)
|
||||
except ValueError:
|
||||
except (ValueError, TypeError):
|
||||
# not uuid
|
||||
if not os.path.exists(path):
|
||||
return False, "Path doesn't exist or invalid token"
|
||||
|
|
|
|||
20
pype.py
20
pype.py
|
|
@ -86,16 +86,21 @@ def boot():
|
|||
use_version = m.group('version')
|
||||
break
|
||||
|
||||
if not os.getenv("AVALON_MONGO"):
|
||||
if not os.getenv("PYPE_MONGO"):
|
||||
try:
|
||||
avalon_mongo = bootstrap.registry.get_secure_item("avalonMongo")
|
||||
pype_mongo = bootstrap.registry.get_secure_item("pypeMongo")
|
||||
except ValueError:
|
||||
print("*** No DB connection string specified.")
|
||||
print("--- launching setup UI ...")
|
||||
import igniter
|
||||
igniter.run()
|
||||
set_environments()
|
||||
return
|
||||
else:
|
||||
os.environ["AVALON_MONGO"] = avalon_mongo
|
||||
os.environ["PYPE_MONGO"] = pype_mongo
|
||||
|
||||
# FIXME (antirotor): we need to set those in different way
|
||||
if not os.getenv("AVALON_MONGO"):
|
||||
os.environ["AVALON_MONGO"] = os.environ["PYPE_MONGO"]
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
if not pype_versions:
|
||||
|
|
@ -118,6 +123,13 @@ def boot():
|
|||
else:
|
||||
# run through repos and add them to sys.path and PYTHONPATH
|
||||
pype_root = os.path.dirname(os.path.realpath(__file__))
|
||||
local_version = bootstrap.get_local_version()
|
||||
if use_version and use_version != local_version:
|
||||
if use_version in pype_versions.keys():
|
||||
# use specified
|
||||
bootstrap.add_paths_from_archive(pype_versions[use_version])
|
||||
use_version = pype_versions[use_version]
|
||||
|
||||
os.environ["PYPE_ROOT"] = pype_root
|
||||
repos = os.listdir(os.path.join(pype_root, "repos"))
|
||||
repos = [os.path.join(pype_root, "repos", repo) for repo in repos]
|
||||
|
|
|
|||
|
|
@ -321,7 +321,8 @@ class JSONSettingRegistry(ASettingRegistry):
|
|||
"registry": {}
|
||||
}
|
||||
|
||||
self._registry_file.parent.mkdir(parents=True)
|
||||
if not self._registry_file.parent.exists():
|
||||
self._registry_file.parent.mkdir(parents=True)
|
||||
if not os.path.exists(self._registry_file):
|
||||
with open(self._registry_file, mode="w") as cfg:
|
||||
json.dump(header, cfg, indent=4)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Implementation of Pype commands."""
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from pype.lib import execute
|
||||
from pype.lib import PypeLogger as Logger
|
||||
|
||||
|
||||
class PypeCommands:
|
||||
|
|
@ -7,9 +14,43 @@ class PypeCommands:
|
|||
|
||||
Most of its methods are called by :mod:`cli` module.
|
||||
"""
|
||||
@staticmethod
|
||||
def launch_tray(debug):
|
||||
if debug:
|
||||
execute([
|
||||
sys.executable,
|
||||
"-m",
|
||||
"pype.tools.tray"
|
||||
])
|
||||
return
|
||||
|
||||
detached_process = 0x00000008 # noqa: N806
|
||||
|
||||
args = [sys.executable, "-m", "pype.tools.tray"]
|
||||
if sys.platform.startswith('linux'):
|
||||
subprocess.Popen(
|
||||
args,
|
||||
universal_newlines=True,
|
||||
bufsize=1,
|
||||
env=os.environ,
|
||||
stdout=None,
|
||||
stderr=None,
|
||||
preexec_fn=os.setpgrp
|
||||
)
|
||||
|
||||
if sys.platform == 'win32':
|
||||
args = ["pythonw", "-m", "pype.tools.tray"]
|
||||
subprocess.Popen(
|
||||
args,
|
||||
universal_newlines=True,
|
||||
bufsize=1,
|
||||
cwd=None,
|
||||
env=os.environ,
|
||||
stdout=open(Logger.get_file_path(), 'w+'),
|
||||
stderr=subprocess.STDOUT,
|
||||
creationflags=detached_process
|
||||
)
|
||||
|
||||
def launch_tray(self, debug):
|
||||
pass
|
||||
|
||||
def launch_eventservercli(self, args):
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -118,3 +118,9 @@ def test_find_pype(fix_bootstrap, tmp_path_factory, monkeypatch, printer):
|
|||
assert list(result.values())[-1] == Path(
|
||||
r_path / test_versions_2[0]
|
||||
), "not a latest version of Pype 2"
|
||||
|
||||
result = fix_bootstrap.find_pype(e_path)
|
||||
assert result is not None, "no Pype version found"
|
||||
assert list(result.values())[-1] == Path(
|
||||
e_path / test_versions_1[1]
|
||||
), "not a latest version of Pype 1"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue