-
Notifications
You must be signed in to change notification settings - Fork 12
workfile comparison and better database support #78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
13e8b45
759c860
fde27ae
d6af08f
a1283b2
a5df833
ac08747
6bfb14f
8e4e46e
d84c777
81bb143
bf35006
c3d9adf
46e90ef
3642a79
49455c2
916420d
4a2eeaf
eb3fd7f
96a46fb
e88c06f
1f090e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import sys | ||
| from pathlib import Path | ||
| from qtpy import QtWidgets, QtCore, QtGui | ||
|
|
||
| from ayon_core.style import load_stylesheet | ||
|
|
||
|
|
||
| class SplashWidget(QtWidgets.QWidget): | ||
| def __init__(self): | ||
| super(SplashWidget, self).__init__() | ||
| self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.FramelessWindowHint) | ||
|
|
||
| layout = QtWidgets.QHBoxLayout(self) | ||
| layout.setContentsMargins(10, 10, 10, 10) | ||
|
|
||
| lbl_spinner = QtWidgets.QLabel() | ||
| gif_path = Path(__file__).parent / "ayon_spinner_white_pong_360.gif" | ||
| movie = QtGui.QMovie(gif_path.as_posix()) | ||
| movie.setScaledSize(QtCore.QSize(64, 64)) | ||
| lbl_spinner.setMovie(movie) | ||
| movie.start() | ||
| layout.addWidget(lbl_spinner) | ||
|
|
||
| lbl_info = QtWidgets.QLabel("Please wait...\nYour workfile is being opened.") | ||
| font = lbl_info.font() | ||
| font.setPointSize(16) | ||
| lbl_info.setFont(font) | ||
| layout.addWidget(lbl_info) | ||
|
|
||
| self.center_on_screen() | ||
|
|
||
| def center_on_screen(self): | ||
| if hasattr(QtWidgets.QApplication, "desktop"): | ||
| # Qt5 / PySide2 | ||
| desktop = QtWidgets.QApplication.desktop() | ||
| screen_rect = desktop.screenGeometry(desktop.primaryScreen()) | ||
| else: | ||
| # Qt6 / PySide6 | ||
| screen = QtWidgets.QApplication.primaryScreen() | ||
| screen_rect = screen.geometry() | ||
|
|
||
| self.adjustSize() | ||
| window_rect = self.geometry() | ||
| self.move( | ||
| int(screen_rect.center().x() - window_rect.width() / 2), | ||
| int(screen_rect.center().y() - window_rect.height() / 2) | ||
| ) | ||
|
|
||
|
|
||
| def main(): | ||
| app = QtWidgets.QApplication.instance() | ||
| if not app: | ||
| app = QtWidgets.QApplication([]) | ||
|
|
||
| app.setStyleSheet(load_stylesheet()) | ||
|
|
||
| widget = SplashWidget() | ||
| widget.show() | ||
| sys.exit(app.exec_()) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| main() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,23 @@ | ||
| """Host API required Work Files tool""" | ||
|
|
||
| import os | ||
| import sys | ||
| import time | ||
| import hashlib | ||
| from pathlib import Path | ||
|
|
||
| from qtpy import QtWidgets | ||
|
|
||
| from ayon_core.lib import Logger | ||
| from ayon_core.settings import get_project_settings | ||
| from ayon_core.pipeline.context_tools import get_current_project_name | ||
|
|
||
| from .lib import ( | ||
| get_project_manager, | ||
| get_current_resolve_project | ||
| get_current_resolve_project, | ||
| set_project_manager_to_folder_name | ||
| ) | ||
| from .menu import DatabaseMisconfigurationWarning, ProjectImportChooser | ||
|
|
||
|
|
||
| log = Logger.get_logger(__name__) | ||
|
|
@@ -23,22 +35,54 @@ def has_unsaved_changes(): | |
|
|
||
| def save_file(filepath): | ||
| project_manager = get_project_manager() | ||
| file = os.path.basename(filepath) | ||
| fname, _ = os.path.splitext(file) | ||
| project_saved = project_manager.SaveProject() | ||
| if not project_saved: | ||
| log.error("Failed to save current project!") | ||
| return False | ||
|
|
||
| resolve_project = get_current_resolve_project() | ||
| name = resolve_project.GetName() | ||
| incoming_wf = Path(filepath) | ||
| current_wf = incoming_wf.with_name( | ||
| resolve_project.GetName() + ".drp") | ||
|
|
||
| response = False | ||
| if name == "Untitled Project": | ||
| response = project_manager.CreateProject(fname) | ||
| log.info("New project created: {}".format(response)) | ||
| project_manager.SaveProject() | ||
| elif name != fname: | ||
| response = resolve_project.SetName(fname) | ||
| log.info("Project renamed: {}".format(response)) | ||
| # handle project db override if set | ||
| project_name = get_current_project_name() | ||
| settings = get_project_settings(project_name) | ||
| override_is_valid = True | ||
| if settings["resolve"]["project_db"].get("enabled", False): | ||
| log.info("Handling project database override...") | ||
| overrides = settings["resolve"]["project_db"] | ||
| override_is_valid = handle_project_db_override( | ||
| project_name, overrides | ||
| ) | ||
| if not override_is_valid: | ||
| return False | ||
|
|
||
| exported = project_manager.ExportProject(fname, filepath) | ||
| log.info("Project exported: {}".format(exported)) | ||
| rename_db_project = settings["resolve"].get("rename_db_project_on_increment", True) | ||
| if "Untitled Project" in current_wf.stem: | ||
| # saving initial workfile from currently opened project | ||
| project_manager.CreateProject(incoming_wf.stem) | ||
| project_manager.SaveProject() | ||
| exported = project_manager.ExportProject(incoming_wf.stem, incoming_wf.as_posix()) | ||
| log.info(f"New project {incoming_wf.stem} exported: {exported}") | ||
| if current_wf.stem != incoming_wf.stem: | ||
| # workfile shall be incremented | ||
| if rename_db_project: | ||
| # increment with local renaming | ||
| resolve_project.SetName(incoming_wf.stem) | ||
| exported = project_manager.ExportProject(incoming_wf.stem, incoming_wf.as_posix()) | ||
| log.info(f"Incremented workfile with local rename to {incoming_wf.as_posix()}: {exported}") | ||
| else: | ||
| # increment without local renaming but reimport | ||
| exported = project_manager.ExportProject(current_wf.stem, current_wf.as_posix()) | ||
| exported = project_manager.ExportProject(current_wf.stem, incoming_wf.as_posix()) | ||
| project_manager.ImportProject(incoming_wf.as_posix()) | ||
| project_manager.LoadProject(incoming_wf.stem) | ||
| log.info(f"Incremented workfile with reimport to {incoming_wf.as_posix()}: {exported}") | ||
| else: | ||
| # workfile export without increment | ||
| exported = project_manager.ExportProject(incoming_wf.stem, incoming_wf.as_posix()) | ||
| log.info(f"Project exported without increment to {incoming_wf.as_posix()}: {exported}") | ||
|
|
||
|
|
||
| def open_file(filepath): | ||
|
|
@@ -60,6 +104,24 @@ def open_file(filepath): | |
| file = os.path.basename(filepath) | ||
| fname, _ = os.path.splitext(file) | ||
|
|
||
| # handle project db override if set | ||
| project_name = get_current_project_name() | ||
| settings = get_project_settings(project_name) | ||
| override_is_valid = True | ||
| if settings["resolve"]["project_db"].get("enabled", False): | ||
| log.info("Handling project database override...") | ||
| overrides = settings["resolve"]["project_db"] | ||
| override_is_valid = handle_project_db_override( | ||
| project_name, overrides | ||
| ) | ||
| if not override_is_valid: | ||
| return False | ||
|
|
||
| if settings["resolve"]["project_db"]["db_type"] == "Disk": | ||
| handle_local_vs_exported_project( | ||
| settings, project_name, filepath | ||
| ) | ||
|
|
||
| try: | ||
| # load project from input path | ||
| resolve_project = project_manager.LoadProject(fname) | ||
|
|
@@ -93,5 +155,105 @@ def current_file(): | |
| return os.path.normpath(current_file_path) | ||
|
|
||
|
|
||
| def handle_local_vs_exported_project(settings, project_name, file_path): | ||
| project_manager = get_project_manager() | ||
|
|
||
| file_name = Path(file_path).stem | ||
| if settings["resolve"]["project_db"].get("use_db_project_folder", False): | ||
| db_project = get_local_database_root() / project_name / file_name / "Project.db" | ||
| else: | ||
| db_project = get_local_database_root() / file_name / "Project.db" | ||
|
|
||
| if not db_project.exists(): | ||
| log.warning(f"Project `{file_name}` does not exist in local database. Aborting timestamp comparison.") | ||
| return | ||
|
|
||
| mtime_drp = Path(file_path).stat().st_mtime | ||
| mtime_dbp = db_project.stat().st_mtime if db_project.exists() else 0 | ||
| if mtime_drp > mtime_dbp: | ||
| choice = ProjectImportChooser(mtime_drp, mtime_dbp).exec_() | ||
| if choice == QtWidgets.QMessageBox.Ok: | ||
| proj = project_manager.LoadProject(file_name) | ||
|
tweak-wtf marked this conversation as resolved.
|
||
| if not proj: | ||
| log.warning(f"Failed to load project `{file_name}` for import. Aborting.") | ||
| return | ||
| sha = hashlib.sha1( | ||
| f"{file_name}_{time.time()}".encode("utf-8") | ||
| ).hexdigest()[:6] | ||
| proj_bkp_name = f"{proj.GetName()}_BKP_{sha}" | ||
| proj.SetName(proj_bkp_name) | ||
| project_manager.SaveProject() | ||
| project_manager.CloseProject(proj_bkp_name) | ||
| project_manager.ImportProject(file_path) | ||
|
tweak-wtf marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def handle_project_db_override(project_name, settings) -> bool: | ||
| project_manager = get_project_manager() | ||
|
|
||
| available_dbs = project_manager.GetDatabaseList() or [] | ||
| curr_db = project_manager.GetCurrentDatabase() | ||
|
|
||
| valid_db_settings = False | ||
| for available_db in available_dbs: | ||
| if available_db["DbType"] == settings["db_type"]: | ||
| if available_db["DbName"] == settings["db_name"]: | ||
| # NOTE: Disk databases don't return IP address, so i consider them valid as long as type and name match | ||
| if settings["db_type"] == "Disk": | ||
| valid_db_settings = True | ||
| break | ||
| elif available_db.get("IpAddress", "") == settings.get("db_ip", ""): | ||
| valid_db_settings = True | ||
| break | ||
|
|
||
| if not valid_db_settings: | ||
| DatabaseMisconfigurationWarning( | ||
| settings, available_dbs | ||
| ).exec_() | ||
| return False | ||
|
|
||
| # check if we're already in the right database | ||
| # reloading the database causes projects to not increment correctly anymore | ||
| curr_db_valid = True | ||
| if settings["db_type"] != curr_db.get("DbType", ""): | ||
| curr_db_valid = False | ||
| if settings["db_name"] != curr_db.get("DbName", ""): | ||
| curr_db_valid = False | ||
| if settings["db_type"] != "Disk" and ( | ||
| settings["db_ip"] != curr_db.get("IpAddress", "127.0.0.1") | ||
| ): | ||
| curr_db_valid = False | ||
|
|
||
| if not curr_db_valid: | ||
| db_parms = { | ||
| "DbType": settings["db_type"], | ||
| "DbName": settings["db_name"], | ||
| } | ||
| if settings["db_type"] != "Disk": | ||
| db_parms["IpAddress"] = settings["db_ip"] | ||
| log.info(f"Setting Project Database with Parameters: {db_parms}") | ||
| project_manager.SetCurrentDatabase(db_parms) | ||
| else: | ||
| log.info(f"Using current Project Database: {curr_db}") | ||
|
|
||
| if settings.get("use_db_project_folder", False): | ||
| set_project_manager_to_folder_name(project_name) | ||
|
|
||
| return True | ||
|
|
||
|
|
||
| def get_local_database_root() -> Path: | ||
| # does anyone use resolve users other than guest? | ||
| if sys.platform == "win32": | ||
| result = ( | ||
| Path(os.getenv("APPDATA")) | ||
| / "Blackmagic Design" / "DaVinci Resolve" / "Support" | ||
| / "Resolve Project Library" / "Resolve Projects" | ||
| / "Users" / "guest" / "Projects" | ||
| ) | ||
|
Comment on lines
+244
to
+252
|
||
| else: | ||
| raise NotImplementedError(f"Database path for platform {sys.platform} is not implemented yet.") | ||
| return result | ||
|
tweak-wtf marked this conversation as resolved.
|
||
|
|
||
|
|
||
| def work_root(session): | ||
| return os.path.normpath(session["AYON_WORKDIR"]).replace("\\", "/") | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR description says the local-vs-exported workfile comparison “only triggers when using
Disktype database”, but the implementation only runshandle_local_vs_exported_projectwhen the database override setting is enabled. If the intended behavior is to compare for all Disk databases (even without override), the gating condition should be adjusted; otherwise the PR description should be updated to reflect that this is opt-in via the override setting.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah pretty much the entire PR is opt-in.
handle_local_vs_exported_projectonly runs ondb_type="Disk"because otherwise a shared database is used. in that case there is no need for comparing workfiles as every user is guaranteed to work on the same.