From b4a3038623961e77c25d6784341370d21cbc08bb Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 13 Aug 2021 18:02:12 +0200 Subject: [PATCH 1/9] add `--validate-version` and `--headless` --- igniter/__init__.py | 3 + igniter/bootstrap_repos.py | 124 +++++++++++++++++++++++- openpype/cli.py | 2 + start.py | 73 ++++++++++++-- website/docs/admin_openpype_commands.md | 3 + website/docs/admin_use.md | 13 +++ 6 files changed, 211 insertions(+), 7 deletions(-) diff --git a/igniter/__init__.py b/igniter/__init__.py index 20bf9be106..73e315d88a 100644 --- a/igniter/__init__.py +++ b/igniter/__init__.py @@ -12,6 +12,9 @@ from .version import __version__ as version def open_dialog(): """Show Igniter dialog.""" + if os.getenv("OPENPYPE_HEADLESS_MODE"): + print("!!! Can't open dialog in headless mode. Exiting.") + sys.exit(1) from Qt import QtWidgets, QtCore from .install_dialog import InstallDialog diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index 8c081b8614..22f5e7d94c 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -9,6 +9,7 @@ import sys import tempfile from pathlib import Path from typing import Union, Callable, List, Tuple +import hashlib from zipfile import ZipFile, BadZipFile @@ -28,6 +29,25 @@ LOG_WARNING = 1 LOG_ERROR = 3 +def sha256sum(filename): + """Calculate sha256 for content of the file. + + Args: + filename (str): Path to file. + + Returns: + str: hex encoded sha256 + + """ + h = hashlib.sha256() + b = bytearray(128 * 1024) + mv = memoryview(b) + with open(filename, 'rb', buffering=0) as f: + for n in iter(lambda: f.readinto(mv), 0): + h.update(mv[:n]) + return h.hexdigest() + + class OpenPypeVersion(semver.VersionInfo): """Class for storing information about OpenPype version. @@ -261,7 +281,8 @@ class BootstrapRepos: self.live_repo_dir = Path(Path(__file__).parent / ".." / "repos") @staticmethod - def get_version_path_from_list(version: str, version_list: list) -> Path: + def get_version_path_from_list( + version: str, version_list: list) -> Union[Path, None]: """Get path for specific version in list of OpenPype versions. Args: @@ -275,6 +296,7 @@ class BootstrapRepos: for v in version_list: if str(v) == version: return v.path + return None @staticmethod def get_local_live_version() -> str: @@ -487,6 +509,7 @@ class BootstrapRepos: openpype_root = openpype_path.resolve() # generate list of filtered paths dir_filter = [openpype_root / f for f in self.openpype_filter] + checksums = [] file: Path for file in openpype_list: @@ -508,11 +531,110 @@ class BootstrapRepos: processed_path = file self._print(f"- processing {processed_path}") + checksums.append( + ( + sha256sum(file.as_posix()), + file.resolve().relative_to(openpype_root) + ) + ) zip_file.write(file, file.relative_to(openpype_root)) + checksums_str = "" + for c in checksums: + checksums_str += "{}:{}\n".format(c[0], c[1]) + zip_file.writestr("checksums", checksums_str) # test if zip is ok zip_file.testzip() self._progress_callback(100) + + def validate_openpype_version(self, path: Path) -> tuple: + """Validate version directory or zip file. + + This will load `checksums` file if present, calculate checksums + of existing files in given path and compare. It will also compare + lists of files together for missing files. + + Args: + path (Path): Path to OpenPype version to validate. + + Returns: + tuple(bool, str): with version validity as first item and string with + reason as second. + + """ + if not path.exists(): + return False, "Path doesn't exist" + + if path.is_file(): + return self._validate_zip(path) + return self._validate_dir(path) + + @staticmethod + def _validate_zip(path: Path) -> tuple: + """Validate content of zip file.""" + with ZipFile(path, "r") as zip_file: + # read checksums + try: + checksums_data = str(zip_file.read("checksums")) + except IOError: + # FIXME: This should be set to False sometimes in the future + return True, "Cannot read checksums for archive." + + # split it to the list of tuples + checksums = [ + tuple(line.split(":")) + for line in checksums_data.split("\n") if line + ] + + # calculate and compare checksums in the zip file + for file in checksums: + h = hashlib.sha256() + h.update(zip_file.read(file[1])) + if h.hexdigest() != file[0]: + return False, f"Invalid checksum on {file[1]}" + + # get list of files in zip minus `checksums` file itself + # and turn in to set to compare against list of files + # from checksum file. If difference exists, something is + # wrong + files_in_zip = zip_file.namelist() + files_in_zip.remove("checksums") + files_in_zip = set(files_in_zip) + files_in_checksum = set([file[1] for file in checksums]) + diff = files_in_zip.difference(files_in_checksum) + if diff: + return False, f"Missing files {diff}" + + return True, "All ok" + + @staticmethod + def _validate_dir(path: Path) -> tuple: + checksums_file = Path(path / "checksums") + if not checksums_file.exists(): + # FIXME: This should be set to False sometimes in the future + return True, "Cannot read checksums for archive." + checksums_data = checksums_file.read_text() + checksums = [ + tuple(line.split(":")) + for line in checksums_data.split("\n") if line + ] + files_in_dir = [ + file.relative_to(path).as_posix() + for file in path.iterdir() if file.is_file() + ] + files_in_dir.remove("checksums") + files_in_dir = set(files_in_dir) + files_in_checksum = set([file[1] for file in checksums]) + + for file in checksums: + current = sha256sum((path / file[1]).as_posix()) + if file[0] != current: + return False, f"Invalid checksum on {file[1]}" + diff = files_in_dir.difference(files_in_checksum) + if diff: + return False, f"Missing files {diff}" + + return True, "All ok" @staticmethod def add_paths_from_archive(archive: Path) -> None: diff --git a/openpype/cli.py b/openpype/cli.py index ec5b04c468..be14a8aa7d 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -18,6 +18,8 @@ from .pype_commands import PypeCommands @click.option("--list-versions", is_flag=True, expose_value=False, help=("list all detected versions. Use With `--use-staging " "to list staging versions.")) +@click.option("--validate-version", + help="validate given version integrity") def main(ctx): """Pype is main command serving as entry point to pipeline system. diff --git a/start.py b/start.py index 6473a926d0..ca4b2835bb 100644 --- a/start.py +++ b/start.py @@ -179,8 +179,10 @@ else: ssl_cert_file = certifi.where() os.environ["SSL_CERT_FILE"] = ssl_cert_file +if "--headless" in sys.argv: + os.environ["OPENPYPE_HEADLESS_MODE"] = "1" -import igniter # noqa: E402 +import igniter # noqa: E402 from igniter import BootstrapRepos # noqa: E402 from igniter.tools import ( get_openpype_path_from_db, @@ -343,7 +345,7 @@ def _process_arguments() -> tuple: # check for `--use-version=3.0.0` argument and `--use-staging` use_version = None use_staging = False - print_versions = False + commands = [] for arg in sys.argv: if arg == "--use-version": _print("!!! Please use option --use-version like:") @@ -366,12 +368,30 @@ def _process_arguments() -> tuple: " proper version string.")) sys.exit(1) + if arg == "--validate-version": + _print("!!! Please use option --validate-version like:") + _print(" --validate-version=3.0.0") + sys.exit(1) + + if arg.startswith("--validate-version="): + m = re.search( + r"--validate-version=(?P\d+\.\d+\.\d+(?:\S*)?)", arg) + if m and m.group('version'): + use_version = m.group('version') + sys.argv.remove(arg) + commands.append("validate") + else: + _print("!!! Requested version isn't in correct format.") + _print((" Use --list-versions to find out" + " proper version string.")) + sys.exit(1) + if "--use-staging" in sys.argv: use_staging = True sys.argv.remove("--use-staging") if "--list-versions" in sys.argv: - print_versions = True + commands.append("print_versions") sys.argv.remove("--list-versions") # handle igniter @@ -389,7 +409,7 @@ def _process_arguments() -> tuple: sys.argv.pop(idx) sys.argv.insert(idx, "tray") - return use_version, use_staging, print_versions + return use_version, use_staging, commands def _determine_mongodb() -> str: @@ -738,7 +758,7 @@ def boot(): # Process arguments # ------------------------------------------------------------------------ - use_version, use_staging, print_versions = _process_arguments() + use_version, use_staging, commands = _process_arguments() if os.getenv("OPENPYPE_VERSION"): if use_version: @@ -766,13 +786,47 @@ def boot(): # Get openpype path from database and set it to environment so openpype can # find its versions there and bootstrap them. openpype_path = get_openpype_path_from_db(openpype_mongo) + + if getattr(sys, 'frozen', False): + local_version = bootstrap.get_version(Path(OPENPYPE_ROOT)) + else: + local_version = bootstrap.get_local_live_version() + + if "validate" in commands: + _print(f">>> Validating version [ {use_version} ]") + openpype_versions = bootstrap.find_openpype(include_zips=True, + staging=True) + openpype_versions += bootstrap.find_openpype(include_zips=True, + staging=False) + + v: OpenPypeVersion + found = [v for v in openpype_versions if str(v) == use_version] + if not found: + _print(f"!!! Version [ {use_version} ] not found.") + list_versions(openpype_versions, local_version) + sys.exit(1) + + # print result + result = bootstrap.validate_openpype_version( + bootstrap.get_version_path_from_list( + use_version, openpype_versions)) + + _print("{}{}".format( + ">>> " if result[0] else "!!! ", + bootstrap.validate_openpype_version( + bootstrap.get_version_path_from_list(use_version, openpype_versions) + )[1]) + ) + sys.exit(1) + + if not openpype_path: _print("*** Cannot get OpenPype path from database.") if not os.getenv("OPENPYPE_PATH") and openpype_path: os.environ["OPENPYPE_PATH"] = openpype_path - if print_versions: + if "print_versions" in commands: if not use_staging: _print("--- This will list only non-staging versions detected.") _print(" To see staging versions, use --use-staging argument.") @@ -803,6 +857,13 @@ def boot(): # no version to run _print(f"!!! {e}") sys.exit(1) + # validate version + _print(f">>> Validating version [ {str(version_path)} ]") + result = bootstrap.validate_openpype_version(version_path) + if not result[0]: + _print(f"!!! Invalid version: {result[1]}") + sys.exit(1) + _print(f"--- version is valid") else: version_path = _bootstrap_from_code(use_version, use_staging) diff --git a/website/docs/admin_openpype_commands.md b/website/docs/admin_openpype_commands.md index 1a91e2e7fe..d6ccc883b0 100644 --- a/website/docs/admin_openpype_commands.md +++ b/website/docs/admin_openpype_commands.md @@ -18,11 +18,14 @@ Running OpenPype without any commands will default to `tray`. ```shell openpype_console --use-version=3.0.0-foo+bar ``` +`--headless` - to run OpenPype in headless mode (without using graphical UI) `--use-staging` - to use staging versions of OpenPype. `--list-versions [--use-staging]` - to list available versions. +`--validate-version` to validate integrity of given version + For more information [see here](admin_use#run-openpype). ## Commands diff --git a/website/docs/admin_use.md b/website/docs/admin_use.md index 4ad08a0174..178241ad19 100644 --- a/website/docs/admin_use.md +++ b/website/docs/admin_use.md @@ -56,6 +56,19 @@ openpype_console --list-versions You can add `--use-staging` to list staging versions. ::: +If you want to validate integrity of some available version, you can use: + +```shell +openpype_console --validate-version=3.3.0 +``` + +This will go through the version and validate file content against sha 256 hashes +stored in `checksums` file. + +:::tip Headless mode +Add `--headless` to run OpenPype without graphical UI (useful on server or on automated tasks, etc.) +::: + ### Details When you run OpenPype from executable, few check are made: From b6d831045796656f4bbd4ed98fbe256d22704295 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 13 Aug 2021 18:18:57 +0200 Subject: [PATCH 2/9] hound fixes, checks for missing files --- igniter/bootstrap_repos.py | 19 +++++++++++++------ start.py | 8 ++++---- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index 22f5e7d94c..535bb723bc 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -546,7 +546,7 @@ class BootstrapRepos: # test if zip is ok zip_file.testzip() self._progress_callback(100) - + def validate_openpype_version(self, path: Path) -> tuple: """Validate version directory or zip file. @@ -558,13 +558,13 @@ class BootstrapRepos: path (Path): Path to OpenPype version to validate. Returns: - tuple(bool, str): with version validity as first item and string with - reason as second. + tuple(bool, str): with version validity as first item + and string with reason as second. """ if not path.exists(): return False, "Path doesn't exist" - + if path.is_file(): return self._validate_zip(path) return self._validate_dir(path) @@ -589,7 +589,10 @@ class BootstrapRepos: # calculate and compare checksums in the zip file for file in checksums: h = hashlib.sha256() - h.update(zip_file.read(file[1])) + try: + h.update(zip_file.read(file[1])) + except FileNotFoundError: + return False, f"Missing file [ {file[1]} ]" if h.hexdigest() != file[0]: return False, f"Invalid checksum on {file[1]}" @@ -627,7 +630,11 @@ class BootstrapRepos: files_in_checksum = set([file[1] for file in checksums]) for file in checksums: - current = sha256sum((path / file[1]).as_posix()) + try: + current = sha256sum((path / file[1]).as_posix()) + except FileNotFoundError: + return False, f"Missing file [ {file[1]} ]" + if file[0] != current: return False, f"Invalid checksum on {file[1]}" diff = files_in_dir.difference(files_in_checksum) diff --git a/start.py b/start.py index ca4b2835bb..a5f662d39b 100644 --- a/start.py +++ b/start.py @@ -182,7 +182,7 @@ else: if "--headless" in sys.argv: os.environ["OPENPYPE_HEADLESS_MODE"] = "1" -import igniter # noqa: E402 +import igniter # noqa: E402 from igniter import BootstrapRepos # noqa: E402 from igniter.tools import ( get_openpype_path_from_db, @@ -797,8 +797,7 @@ def boot(): openpype_versions = bootstrap.find_openpype(include_zips=True, staging=True) openpype_versions += bootstrap.find_openpype(include_zips=True, - staging=False) - + staging=False) v: OpenPypeVersion found = [v for v in openpype_versions if str(v) == use_version] if not found: @@ -814,7 +813,8 @@ def boot(): _print("{}{}".format( ">>> " if result[0] else "!!! ", bootstrap.validate_openpype_version( - bootstrap.get_version_path_from_list(use_version, openpype_versions) + bootstrap.get_version_path_from_list( + use_version, openpype_versions) )[1]) ) sys.exit(1) From df310da1411ef10331daad7a0271efe1cb7cf429 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 20 Aug 2021 17:57:28 +0200 Subject: [PATCH 3/9] add update dialog --- igniter/__init__.py | 23 +++++ igniter/bootstrap_repos.py | 6 ++ igniter/update_thread.py | 61 +++++++++++++ igniter/update_window.py | 173 +++++++++++++++++++++++++++++++++++++ openpype/cli.py | 2 +- start.py | 12 ++- 6 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 igniter/update_thread.py create mode 100644 igniter/update_window.py diff --git a/igniter/__init__.py b/igniter/__init__.py index 73e315d88a..defd45e233 100644 --- a/igniter/__init__.py +++ b/igniter/__init__.py @@ -31,8 +31,31 @@ def open_dialog(): return d.result() +def open_update_window(openpype_version): + """Open update window.""" + if os.getenv("OPENPYPE_HEADLESS_MODE"): + print("!!! Can't open dialog in headless mode. Exiting.") + sys.exit(1) + from Qt import QtWidgets, QtCore + from .update_window import UpdateWindow + + scale_attr = getattr(QtCore.Qt, "AA_EnableHighDpiScaling", None) + if scale_attr is not None: + QtWidgets.QApplication.setAttribute(scale_attr) + + app = QtWidgets.QApplication(sys.argv) + + d = UpdateWindow(version=openpype_version) + d.open() + + app.exec_() + version_path = d.get_version_path() + return version_path + + __all__ = [ "BootstrapRepos", "open_dialog", + "open_update_window", "version" ] diff --git a/igniter/bootstrap_repos.py b/igniter/bootstrap_repos.py index 535bb723bc..c527de0066 100644 --- a/igniter/bootstrap_repos.py +++ b/igniter/bootstrap_repos.py @@ -966,6 +966,7 @@ class BootstrapRepos: # test if destination directory already exist, if so lets delete it. if destination.exists() and force: + self._print("removing existing directory") try: shutil.rmtree(destination) except OSError as e: @@ -975,6 +976,7 @@ class BootstrapRepos: raise OpenPypeVersionIOError( f"cannot remove existing {destination}") from e elif destination.exists() and not force: + self._print("destination directory already exists") raise OpenPypeVersionExists(f"{destination} already exist.") else: # create destination parent directories even if they don't exist. @@ -984,6 +986,7 @@ class BootstrapRepos: if openpype_version.path.is_dir(): # create zip inside temporary directory. self._print("Creating zip from directory ...") + self._progress_callback(0) with tempfile.TemporaryDirectory() as temp_dir: temp_zip = \ Path(temp_dir) / f"openpype-v{openpype_version}.zip" @@ -1009,13 +1012,16 @@ class BootstrapRepos: raise OpenPypeVersionInvalid("Invalid file format") if not self.is_inside_user_data(openpype_version.path): + self._progress_callback(35) openpype_version.path = self._copy_zip( openpype_version.path, destination) # extract zip there self._print("extracting zip to destination ...") with ZipFile(openpype_version.path, "r") as zip_ref: + self._progress_callback(75) zip_ref.extractall(destination) + self._progress_callback(100) return destination diff --git a/igniter/update_thread.py b/igniter/update_thread.py new file mode 100644 index 0000000000..f4fc729faf --- /dev/null +++ b/igniter/update_thread.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +"""Working thread for update.""" +from Qt.QtCore import QThread, Signal, QObject # noqa + +from .bootstrap_repos import ( + BootstrapRepos, + OpenPypeVersion +) + + +class UpdateThread(QThread): + """Install Worker thread. + + This class takes care of finding OpenPype version on user entered path + (or loading this path from database). If nothing is entered by user, + OpenPype will create its zip files from repositories that comes with it. + + If path contains plain repositories, they are zipped and installed to + user data dir. + + """ + progress = Signal(int) + message = Signal((str, bool)) + + def __init__(self, parent=None): + self._result = None + self._openpype_version = None + QThread.__init__(self, parent) + + def set_version(self, openpype_version: OpenPypeVersion): + self._openpype_version = openpype_version + + def result(self): + """Result of finished installation.""" + return self._result + + def _set_result(self, value): + if self._result is not None: + raise AssertionError("BUG: Result was set more than once!") + self._result = value + + def run(self): + """Thread entry point. + + Using :class:`BootstrapRepos` to either install OpenPype as zip files + or copy them from location specified by user or retrieved from + database. + """ + bs = BootstrapRepos( + progress_callback=self.set_progress, message=self.message) + version_path = bs.install_version(self._openpype_version) + self._set_result(version_path) + + def set_progress(self, progress: int) -> None: + """Helper to set progress bar. + + Args: + progress (int): Progress in percents. + + """ + self.progress.emit(progress) diff --git a/igniter/update_window.py b/igniter/update_window.py new file mode 100644 index 0000000000..2edb3f2c6b --- /dev/null +++ b/igniter/update_window.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- +"""Progress window to show when OpenPype is updating/installing locally.""" +import os +import sys +from pathlib import Path +from .update_thread import UpdateThread +from Qt import QtCore, QtGui, QtWidgets # noqa +from .bootstrap_repos import OpenPypeVersion + + +def load_stylesheet(path: str = None) -> str: + """Load css style sheet. + + Args: + path (str, optional): Path to stylesheet. If none, `stylesheet.css` from + current package's path is used. + Returns: + str: content of the stylesheet + + """ + if path: + stylesheet_path = Path(path) + else: + stylesheet_path = Path(os.path.dirname(__file__)) / "stylesheet.css" + + return stylesheet_path.read_text() + + +class NiceProgressBar(QtWidgets.QProgressBar): + def __init__(self, parent=None): + super(NiceProgressBar, self).__init__(parent) + self._real_value = 0 + + def setValue(self, value): + self._real_value = value + if value != 0 and value < 11: + value = 11 + + super(NiceProgressBar, self).setValue(value) + + def value(self): + return self._real_value + + def text(self): + return "{} %".format(self._real_value) + + +class UpdateWindow(QtWidgets.QDialog): + """OpenPype update window.""" + + _width = 500 + _height = 100 + + def __init__(self, version: OpenPypeVersion, parent=None): + super(UpdateWindow, self).__init__(parent) + self._openpype_version = version + self._result_version_path = None + + self.setWindowTitle( + f"OpenPype is updating ..." + ) + self.setModal(True) + self.setWindowFlags( + QtCore.Qt.WindowMinimizeButtonHint + ) + + current_dir = os.path.dirname(os.path.abspath(__file__)) + roboto_font_path = os.path.join(current_dir, "RobotoMono-Regular.ttf") + poppins_font_path = os.path.join(current_dir, "Poppins") + icon_path = os.path.join(current_dir, "openpype_icon.png") + + # Install roboto font + QtGui.QFontDatabase.addApplicationFont(roboto_font_path) + for filename in os.listdir(poppins_font_path): + if os.path.splitext(filename)[1] == ".ttf": + QtGui.QFontDatabase.addApplicationFont(filename) + + # Load logo + pixmap_openpype_logo = QtGui.QPixmap(icon_path) + # Set logo as icon of window + self.setWindowIcon(QtGui.QIcon(pixmap_openpype_logo)) + + self._pixmap_openpype_logo = pixmap_openpype_logo + + self._update_thread = None + + self.resize(QtCore.QSize(self._width, self._height)) + self._init_ui() + + # Set stylesheet + self.setStyleSheet(load_stylesheet()) + self._run_update() + + def _init_ui(self): + + # Main info + # -------------------------------------------------------------------- + main_label = QtWidgets.QLabel( + f"OpenPype is updating to {self._openpype_version}", self) + main_label.setWordWrap(True) + main_label.setObjectName("MainLabel") + + # Progress bar + # -------------------------------------------------------------------- + progress_bar = NiceProgressBar(self) + progress_bar.setAlignment(QtCore.Qt.AlignCenter) + progress_bar.setTextVisible(False) + + # add all to main + main = QtWidgets.QVBoxLayout(self) + main.addSpacing(15) + main.addWidget(main_label, 0) + main.addSpacing(15) + main.addWidget(progress_bar, 0) + main.addSpacing(15) + + self._progress_bar = progress_bar + + def _run_update(self): + """Start install process. + + This will once again validate entered path and mongo if ok, start + working thread that will do actual job. + """ + # Check if install thread is not already running + if self._update_thread and self._update_thread.isRunning(): + return + self._progress_bar.setRange(0, 0) + update_thread = UpdateThread(self) + update_thread.set_version(self._openpype_version) + update_thread.message.connect(self.update_console) + update_thread.progress.connect(self._update_progress) + update_thread.finished.connect(self._installation_finished) + + self._update_thread = update_thread + + update_thread.start() + + def get_version_path(self): + return self._result_version_path + + def _installation_finished(self): + status = self._update_thread.result() + self._result_version_path = status + self._progress_bar.setRange(0, 1) + self._update_progress(100) + QtWidgets.QApplication.processEvents() + self.done(0) + + def _update_progress(self, progress: int): + # not updating progress as we are not able to determine it + # correctly now. Progress bar is set to un-deterministic mode + # until we are able to get progress in better way. + """ + self._progress_bar.setRange(0, 0) + self._progress_bar.setValue(progress) + text_visible = self._progress_bar.isTextVisible() + if progress == 0: + if text_visible: + self._progress_bar.setTextVisible(False) + elif not text_visible: + self._progress_bar.setTextVisible(True) + """ + return + + def update_console(self, msg: str, error: bool = False) -> None: + """Display message in console. + + Args: + msg (str): message. + error (bool): if True, print it red. + """ + print(msg) diff --git a/openpype/cli.py b/openpype/cli.py index be14a8aa7d..632c3d3386 100644 --- a/openpype/cli.py +++ b/openpype/cli.py @@ -18,7 +18,7 @@ from .pype_commands import PypeCommands @click.option("--list-versions", is_flag=True, expose_value=False, help=("list all detected versions. Use With `--use-staging " "to list staging versions.")) -@click.option("--validate-version", +@click.option("--validate-version", expose_value=False, help="validate given version integrity") def main(ctx): """Pype is main command serving as entry point to pipeline system. diff --git a/start.py b/start.py index a5f662d39b..ca48fdf3b7 100644 --- a/start.py +++ b/start.py @@ -610,8 +610,16 @@ def _find_frozen_openpype(use_version: str = None, if not is_inside: # install latest version to user data dir - version_path = bootstrap.install_version( - openpype_version, force=True) + if not os.getenv("OPENPYPE_HEADLESS"): + import igniter + version_path = igniter.open_update_window(openpype_version) + else: + version_path = bootstrap.install_version( + openpype_version, force=True) + + openpype_version.path = version_path + _initialize_environment(openpype_version) + return openpype_version.path if openpype_version.path.is_file(): _print(">>> Extracting zip file ...") From 2b11e589c5610d5d9ab0232ed8a17bf0ca295949 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 20 Aug 2021 18:06:57 +0200 Subject: [PATCH 4/9] handle igniter dialog --- igniter/update_window.py | 5 ++--- start.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/igniter/update_window.py b/igniter/update_window.py index 2edb3f2c6b..a49a84cfee 100644 --- a/igniter/update_window.py +++ b/igniter/update_window.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Progress window to show when OpenPype is updating/installing locally.""" import os -import sys from pathlib import Path from .update_thread import UpdateThread from Qt import QtCore, QtGui, QtWidgets # noqa @@ -12,8 +11,8 @@ def load_stylesheet(path: str = None) -> str: """Load css style sheet. Args: - path (str, optional): Path to stylesheet. If none, `stylesheet.css` from - current package's path is used. + path (str, optional): Path to stylesheet. If none, `stylesheet.css` + from current package's path is used. Returns: str: content of the stylesheet diff --git a/start.py b/start.py index ca48fdf3b7..27dc105394 100644 --- a/start.py +++ b/start.py @@ -397,6 +397,9 @@ def _process_arguments() -> tuple: # handle igniter # this is helper to run igniter before anything else if "igniter" in sys.argv: + if os.getenv("OPENPYPE_HEADLESS"): + _print("!!! Cannot open Igniter dialog in headless mode.") + sys.exit(1) import igniter return_code = igniter.open_dialog() @@ -444,6 +447,11 @@ def _determine_mongodb() -> str: if not openpype_mongo: _print("*** No DB connection string specified.") + if os.getenv("OPENPYPE_HEADLESS"): + _print("!!! Cannot open Igniter dialog in headless mode.") + _print( + "!!! Please use `OPENPYPE_MONGO` to specify server address.") + sys.exit(1) _print("--- launching setup UI ...") result = igniter.open_dialog() @@ -547,6 +555,9 @@ def _find_frozen_openpype(use_version: str = None, except IndexError: # no OpenPype version found, run Igniter and ask for them. _print('*** No OpenPype versions found.') + if os.getenv("OPENPYPE_HEADLESS"): + _print("!!! Cannot open Igniter dialog in headless mode.") + sys.exit(1) _print("--- launching setup UI ...") import igniter return_code = igniter.open_dialog() From fcb2640c9492722171270b6328469c71dbbcf7c2 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Fri, 20 Aug 2021 18:12:38 +0200 Subject: [PATCH 5/9] fix env var name --- start.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/start.py b/start.py index 27dc105394..9e60d79f04 100644 --- a/start.py +++ b/start.py @@ -397,7 +397,7 @@ def _process_arguments() -> tuple: # handle igniter # this is helper to run igniter before anything else if "igniter" in sys.argv: - if os.getenv("OPENPYPE_HEADLESS"): + if os.getenv("OPENPYPE_HEADLESS_MODE"): _print("!!! Cannot open Igniter dialog in headless mode.") sys.exit(1) import igniter @@ -447,7 +447,7 @@ def _determine_mongodb() -> str: if not openpype_mongo: _print("*** No DB connection string specified.") - if os.getenv("OPENPYPE_HEADLESS"): + if os.getenv("OPENPYPE_HEADLESS_MODE"): _print("!!! Cannot open Igniter dialog in headless mode.") _print( "!!! Please use `OPENPYPE_MONGO` to specify server address.") @@ -555,7 +555,7 @@ def _find_frozen_openpype(use_version: str = None, except IndexError: # no OpenPype version found, run Igniter and ask for them. _print('*** No OpenPype versions found.') - if os.getenv("OPENPYPE_HEADLESS"): + if os.getenv("OPENPYPE_HEADLESS_MODE"): _print("!!! Cannot open Igniter dialog in headless mode.") sys.exit(1) _print("--- launching setup UI ...") @@ -621,7 +621,7 @@ def _find_frozen_openpype(use_version: str = None, if not is_inside: # install latest version to user data dir - if not os.getenv("OPENPYPE_HEADLESS"): + if not os.getenv("OPENPYPE_HEADLESS_MODE"): import igniter version_path = igniter.open_update_window(openpype_version) else: From 901e5f52666f36f7accbc95ace2d9abbc4f6c993 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 31 Aug 2021 18:24:24 +0200 Subject: [PATCH 6/9] refactor common code, change handling of env var --- igniter/install_dialog.py | 33 +++--------------------------- igniter/nice_progress_bar.py | 20 ++++++++++++++++++ igniter/tools.py | 12 +++++++++++ igniter/update_window.py | 39 ++---------------------------------- start.py | 12 +++++++---- 5 files changed, 45 insertions(+), 71 deletions(-) create mode 100644 igniter/nice_progress_bar.py diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index 1ec8cc6768..1fe67e3397 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -14,21 +14,13 @@ from .tools import ( validate_mongo_connection, get_openpype_path_from_db ) + +from .nice_progress_bar import NiceProgressBar from .user_settings import OpenPypeSecureRegistry +from .tools import load_stylesheet from .version import __version__ -def load_stylesheet(): - stylesheet_path = os.path.join( - os.path.dirname(__file__), - "stylesheet.css" - ) - with open(stylesheet_path, "r") as file_stream: - stylesheet = file_stream.read() - - return stylesheet - - class ButtonWithOptions(QtWidgets.QFrame): option_clicked = QtCore.Signal(str) @@ -91,25 +83,6 @@ class ButtonWithOptions(QtWidgets.QFrame): self.option_clicked.emit(self._default_value) -class NiceProgressBar(QtWidgets.QProgressBar): - def __init__(self, parent=None): - super(NiceProgressBar, self).__init__(parent) - self._real_value = 0 - - def setValue(self, value): - self._real_value = value - if value != 0 and value < 11: - value = 11 - - super(NiceProgressBar, self).setValue(value) - - def value(self): - return self._real_value - - def text(self): - return "{} %".format(self._real_value) - - class ConsoleWidget(QtWidgets.QWidget): def __init__(self, parent=None): super(ConsoleWidget, self).__init__(parent) diff --git a/igniter/nice_progress_bar.py b/igniter/nice_progress_bar.py new file mode 100644 index 0000000000..47d695a101 --- /dev/null +++ b/igniter/nice_progress_bar.py @@ -0,0 +1,20 @@ +from Qt import QtCore, QtGui, QtWidgets # noqa + + +class NiceProgressBar(QtWidgets.QProgressBar): + def __init__(self, parent=None): + super(NiceProgressBar, self).__init__(parent) + self._real_value = 0 + + def setValue(self, value): + self._real_value = value + if value != 0 and value < 11: + value = 11 + + super(NiceProgressBar, self).setValue(value) + + def value(self): + return self._real_value + + def text(self): + return "{} %".format(self._real_value) diff --git a/igniter/tools.py b/igniter/tools.py index 529d535c25..c0fa97d03e 100644 --- a/igniter/tools.py +++ b/igniter/tools.py @@ -248,3 +248,15 @@ def get_openpype_path_from_db(url: str) -> Union[str, None]: if os.path.exists(path): return path return None + + +def load_stylesheet() -> str: + """Load css style sheet. + + Returns: + str: content of the stylesheet + + """ + stylesheet_path = Path(__file__).parent.resolve() / "stylesheet.css" + + return stylesheet_path.read_text() \ No newline at end of file diff --git a/igniter/update_window.py b/igniter/update_window.py index a49a84cfee..e443201e09 100644 --- a/igniter/update_window.py +++ b/igniter/update_window.py @@ -5,43 +5,8 @@ from pathlib import Path from .update_thread import UpdateThread from Qt import QtCore, QtGui, QtWidgets # noqa from .bootstrap_repos import OpenPypeVersion - - -def load_stylesheet(path: str = None) -> str: - """Load css style sheet. - - Args: - path (str, optional): Path to stylesheet. If none, `stylesheet.css` - from current package's path is used. - Returns: - str: content of the stylesheet - - """ - if path: - stylesheet_path = Path(path) - else: - stylesheet_path = Path(os.path.dirname(__file__)) / "stylesheet.css" - - return stylesheet_path.read_text() - - -class NiceProgressBar(QtWidgets.QProgressBar): - def __init__(self, parent=None): - super(NiceProgressBar, self).__init__(parent) - self._real_value = 0 - - def setValue(self, value): - self._real_value = value - if value != 0 and value < 11: - value = 11 - - super(NiceProgressBar, self).setValue(value) - - def value(self): - return self._real_value - - def text(self): - return "{} %".format(self._real_value) +from .nice_progress_bar import NiceProgressBar +from .tools import load_stylesheet class UpdateWindow(QtWidgets.QDialog): diff --git a/start.py b/start.py index 9e60d79f04..2e45dc4df3 100644 --- a/start.py +++ b/start.py @@ -181,6 +181,10 @@ else: if "--headless" in sys.argv: os.environ["OPENPYPE_HEADLESS_MODE"] = "1" + sys.argv.remove("--headless") +else: + if os.getenv("OPENPYPE_HEADLESS_MODE") != "1": + os.environ.pop("OPENPYPE_HEADLESS_MODE") import igniter # noqa: E402 from igniter import BootstrapRepos # noqa: E402 @@ -397,7 +401,7 @@ def _process_arguments() -> tuple: # handle igniter # this is helper to run igniter before anything else if "igniter" in sys.argv: - if os.getenv("OPENPYPE_HEADLESS_MODE"): + if os.getenv("OPENPYPE_HEADLESS_MODE") == "1": _print("!!! Cannot open Igniter dialog in headless mode.") sys.exit(1) import igniter @@ -447,7 +451,7 @@ def _determine_mongodb() -> str: if not openpype_mongo: _print("*** No DB connection string specified.") - if os.getenv("OPENPYPE_HEADLESS_MODE"): + if os.getenv("OPENPYPE_HEADLESS_MODE") == "1": _print("!!! Cannot open Igniter dialog in headless mode.") _print( "!!! Please use `OPENPYPE_MONGO` to specify server address.") @@ -555,7 +559,7 @@ def _find_frozen_openpype(use_version: str = None, except IndexError: # no OpenPype version found, run Igniter and ask for them. _print('*** No OpenPype versions found.') - if os.getenv("OPENPYPE_HEADLESS_MODE"): + if os.getenv("OPENPYPE_HEADLESS_MODE") == "1": _print("!!! Cannot open Igniter dialog in headless mode.") sys.exit(1) _print("--- launching setup UI ...") @@ -621,7 +625,7 @@ def _find_frozen_openpype(use_version: str = None, if not is_inside: # install latest version to user data dir - if not os.getenv("OPENPYPE_HEADLESS_MODE"): + if os.getenv("OPENPYPE_HEADLESS_MODE", "0") != "1": import igniter version_path = igniter.open_update_window(openpype_version) else: From b59bb52b6b7abc55e38132cf3175b86489b995cd Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 31 Aug 2021 18:27:26 +0200 Subject: [PATCH 7/9] =?UTF-8?q?hound=20fixes=20=F0=9F=90=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- igniter/tools.py | 2 +- igniter/update_window.py | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/igniter/tools.py b/igniter/tools.py index c0fa97d03e..c934289064 100644 --- a/igniter/tools.py +++ b/igniter/tools.py @@ -259,4 +259,4 @@ def load_stylesheet() -> str: """ stylesheet_path = Path(__file__).parent.resolve() / "stylesheet.css" - return stylesheet_path.read_text() \ No newline at end of file + return stylesheet_path.read_text() diff --git a/igniter/update_window.py b/igniter/update_window.py index e443201e09..d7908c240b 100644 --- a/igniter/update_window.py +++ b/igniter/update_window.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- """Progress window to show when OpenPype is updating/installing locally.""" import os -from pathlib import Path from .update_thread import UpdateThread from Qt import QtCore, QtGui, QtWidgets # noqa from .bootstrap_repos import OpenPypeVersion From e3f0e89e2129eaa99eba436ed5d2fe43402ce77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= <33513211+antirotor@users.noreply.github.com> Date: Wed, 1 Sep 2021 13:05:43 +0200 Subject: [PATCH 8/9] default value for pop Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- start.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/start.py b/start.py index 2e45dc4df3..00f9a50cbb 100644 --- a/start.py +++ b/start.py @@ -184,7 +184,7 @@ if "--headless" in sys.argv: sys.argv.remove("--headless") else: if os.getenv("OPENPYPE_HEADLESS_MODE") != "1": - os.environ.pop("OPENPYPE_HEADLESS_MODE") + os.environ.pop("OPENPYPE_HEADLESS_MODE", None) import igniter # noqa: E402 from igniter import BootstrapRepos # noqa: E402 From e9c2275d046c0b02e82493bddde5df4fb74e1200 Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 2 Sep 2021 14:28:03 +0200 Subject: [PATCH 9/9] remove ftrack submodules --- .gitmodules | 2 +- openpype/modules/ftrack/python2_vendor/arrow | 1 - openpype/modules/ftrack/python2_vendor/ftrack-python-api | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) delete mode 160000 openpype/modules/ftrack/python2_vendor/arrow delete mode 160000 openpype/modules/ftrack/python2_vendor/ftrack-python-api diff --git a/.gitmodules b/.gitmodules index 28f164726d..e1b0917e9d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -9,4 +9,4 @@ url = https://github.com/arrow-py/arrow.git [submodule "openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api"] path = openpype/modules/default_modules/ftrack/python2_vendor/ftrack-python-api - url = https://bitbucket.org/ftrack/ftrack-python-api.git + url = https://bitbucket.org/ftrack/ftrack-python-api.git \ No newline at end of file diff --git a/openpype/modules/ftrack/python2_vendor/arrow b/openpype/modules/ftrack/python2_vendor/arrow deleted file mode 160000 index b746fedf72..0000000000 --- a/openpype/modules/ftrack/python2_vendor/arrow +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b746fedf7286c3755a46f07ab72f4c414cd41fc0 diff --git a/openpype/modules/ftrack/python2_vendor/ftrack-python-api b/openpype/modules/ftrack/python2_vendor/ftrack-python-api deleted file mode 160000 index d277f474ab..0000000000 --- a/openpype/modules/ftrack/python2_vendor/ftrack-python-api +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d277f474ab016e7b53479c36af87cb861d0cc53e