mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #2937 from pypeclub/enhancement/save_as_published_workfiles
Workfiles tool: Save as published workfiles
This commit is contained in:
commit
e5b2e37f1b
5 changed files with 214 additions and 372 deletions
|
|
@ -1269,6 +1269,14 @@ QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical {
|
|||
background: #21252B;
|
||||
}
|
||||
|
||||
/* Workfiles */
|
||||
#WorkfilesPublishedContextSelect {
|
||||
background: rgba(0, 0, 0, 127);
|
||||
}
|
||||
#WorkfilesPublishedContextSelect QLabel {
|
||||
font-size: 17pt;
|
||||
}
|
||||
|
||||
/* Tray */
|
||||
#TrayRestartButton {
|
||||
background: {color:restart-btn-bg};
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ from .model import (
|
|||
DATE_MODIFIED_ROLE,
|
||||
)
|
||||
from .save_as_dialog import SaveAsDialog
|
||||
from .lib import TempPublishFiles
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -45,11 +44,35 @@ class FilesView(QtWidgets.QTreeView):
|
|||
return super(FilesView, self).mouseDoubleClickEvent(event)
|
||||
|
||||
|
||||
class SelectContextOverlay(QtWidgets.QFrame):
|
||||
def __init__(self, parent):
|
||||
super(SelectContextOverlay, self).__init__(parent)
|
||||
|
||||
self.setObjectName("WorkfilesPublishedContextSelect")
|
||||
label_widget = QtWidgets.QLabel(
|
||||
"Please choose context on the left<br/><",
|
||||
self
|
||||
)
|
||||
label_widget.setAlignment(QtCore.Qt.AlignCenter)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.addWidget(label_widget, 1, QtCore.Qt.AlignCenter)
|
||||
|
||||
label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
parent.installEventFilter(self)
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if event.type() == QtCore.QEvent.Resize:
|
||||
self.resize(obj.size())
|
||||
|
||||
return super(SelectContextOverlay, self).eventFilter(obj, event)
|
||||
|
||||
|
||||
class FilesWidget(QtWidgets.QWidget):
|
||||
"""A widget displaying files that allows to save and open files."""
|
||||
file_selected = QtCore.Signal(str)
|
||||
file_opened = QtCore.Signal()
|
||||
publish_file_viewed = QtCore.Signal()
|
||||
workfile_created = QtCore.Signal(str)
|
||||
published_visible_changed = QtCore.Signal(bool)
|
||||
|
||||
|
|
@ -71,9 +94,6 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
self._workfiles_root = None
|
||||
self._workdir_path = None
|
||||
self.host = api.registered_host()
|
||||
temp_publish_files = TempPublishFiles()
|
||||
temp_publish_files.cleanup()
|
||||
self._temp_publish_files = temp_publish_files
|
||||
|
||||
# Whether to automatically select the latest modified
|
||||
# file on a refresh of the files model.
|
||||
|
|
@ -93,14 +113,14 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
|
||||
filter_layout = QtWidgets.QHBoxLayout(filter_widget)
|
||||
filter_layout.setContentsMargins(0, 0, 0, 0)
|
||||
filter_layout.addWidget(published_checkbox, 0)
|
||||
filter_layout.addWidget(filter_input, 1)
|
||||
filter_layout.addWidget(published_checkbox, 0)
|
||||
|
||||
# Create the Files models
|
||||
extensions = set(self.host.file_extensions())
|
||||
|
||||
views_widget = QtWidgets.QWidget(self)
|
||||
# Workarea view
|
||||
# --- Workarea view ---
|
||||
workarea_files_model = WorkAreaFilesModel(extensions)
|
||||
|
||||
# Create proxy model for files to be able sort and filter
|
||||
|
|
@ -118,13 +138,14 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
# Date modified delegate
|
||||
workarea_time_delegate = PrettyTimeDelegate()
|
||||
workarea_files_view.setItemDelegateForColumn(1, workarea_time_delegate)
|
||||
workarea_files_view.setIndentation(3) # smaller indentation
|
||||
# smaller indentation
|
||||
workarea_files_view.setIndentation(3)
|
||||
|
||||
# Default to a wider first filename column it is what we mostly care
|
||||
# about and the date modified is relatively small anyway.
|
||||
workarea_files_view.setColumnWidth(0, 330)
|
||||
|
||||
# Publish files view
|
||||
# --- Publish files view ---
|
||||
publish_files_model = PublishFilesModel(extensions, io, self.anatomy)
|
||||
|
||||
publish_proxy_model = QtCore.QSortFilterProxyModel()
|
||||
|
|
@ -141,12 +162,16 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
# Date modified delegate
|
||||
publish_time_delegate = PrettyTimeDelegate()
|
||||
publish_files_view.setItemDelegateForColumn(1, publish_time_delegate)
|
||||
publish_files_view.setIndentation(3) # smaller indentation
|
||||
# smaller indentation
|
||||
publish_files_view.setIndentation(3)
|
||||
|
||||
# Default to a wider first filename column it is what we mostly care
|
||||
# about and the date modified is relatively small anyway.
|
||||
publish_files_view.setColumnWidth(0, 330)
|
||||
|
||||
publish_context_overlay = SelectContextOverlay(views_widget)
|
||||
publish_context_overlay.setVisible(False)
|
||||
|
||||
views_layout = QtWidgets.QHBoxLayout(views_widget)
|
||||
views_layout.setContentsMargins(0, 0, 0, 0)
|
||||
views_layout.addWidget(workarea_files_view, 1)
|
||||
|
|
@ -155,18 +180,43 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
# Home Page
|
||||
# Build buttons widget for files widget
|
||||
btns_widget = QtWidgets.QWidget(self)
|
||||
btn_save = QtWidgets.QPushButton("Save As", btns_widget)
|
||||
btn_browse = QtWidgets.QPushButton("Browse", btns_widget)
|
||||
btn_open = QtWidgets.QPushButton("Open", btns_widget)
|
||||
|
||||
btn_view_published = QtWidgets.QPushButton("View", btns_widget)
|
||||
workarea_btns_widget = QtWidgets.QWidget(btns_widget)
|
||||
btn_save = QtWidgets.QPushButton("Save As", workarea_btns_widget)
|
||||
btn_browse = QtWidgets.QPushButton("Browse", workarea_btns_widget)
|
||||
btn_open = QtWidgets.QPushButton("Open", workarea_btns_widget)
|
||||
|
||||
workarea_btns_layout = QtWidgets.QHBoxLayout(workarea_btns_widget)
|
||||
workarea_btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
workarea_btns_layout.addWidget(btn_open, 1)
|
||||
workarea_btns_layout.addWidget(btn_browse, 1)
|
||||
workarea_btns_layout.addWidget(btn_save, 1)
|
||||
|
||||
publish_btns_widget = QtWidgets.QWidget(btns_widget)
|
||||
btn_save_as_published = QtWidgets.QPushButton(
|
||||
"Copy && Open", publish_btns_widget
|
||||
)
|
||||
btn_change_context = QtWidgets.QPushButton(
|
||||
"Choose different context", publish_btns_widget
|
||||
)
|
||||
btn_select_context_published = QtWidgets.QPushButton(
|
||||
"Copy && Open", publish_btns_widget
|
||||
)
|
||||
btn_cancel_published = QtWidgets.QPushButton(
|
||||
"Cancel", publish_btns_widget
|
||||
)
|
||||
|
||||
publish_btns_layout = QtWidgets.QHBoxLayout(publish_btns_widget)
|
||||
publish_btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
publish_btns_layout.addWidget(btn_save_as_published, 1)
|
||||
publish_btns_layout.addWidget(btn_change_context, 1)
|
||||
publish_btns_layout.addWidget(btn_select_context_published, 1)
|
||||
publish_btns_layout.addWidget(btn_cancel_published, 1)
|
||||
|
||||
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
|
||||
btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
btns_layout.addWidget(btn_open, 1)
|
||||
btns_layout.addWidget(btn_browse, 1)
|
||||
btns_layout.addWidget(btn_save, 1)
|
||||
btns_layout.addWidget(btn_view_published, 1)
|
||||
btns_layout.addWidget(workarea_btns_widget, 1)
|
||||
btns_layout.addWidget(publish_btns_widget, 1)
|
||||
|
||||
# Build files widgets for home page
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
|
|
@ -188,14 +238,22 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
workarea_files_view.selectionModel().selectionChanged.connect(
|
||||
self.on_file_select
|
||||
)
|
||||
publish_files_view.doubleClickedLeft.connect(
|
||||
self._on_view_published_pressed
|
||||
)
|
||||
|
||||
btn_open.pressed.connect(self._on_workarea_open_pressed)
|
||||
btn_browse.pressed.connect(self.on_browse_pressed)
|
||||
btn_save.pressed.connect(self.on_save_as_pressed)
|
||||
btn_view_published.pressed.connect(self._on_view_published_pressed)
|
||||
btn_save.pressed.connect(self._on_save_as_pressed)
|
||||
btn_save_as_published.pressed.connect(
|
||||
self._on_published_save_as_pressed
|
||||
)
|
||||
btn_change_context.pressed.connect(
|
||||
self._on_publish_change_context_pressed
|
||||
)
|
||||
btn_select_context_published.pressed.connect(
|
||||
self._on_publish_select_context_pressed
|
||||
)
|
||||
btn_cancel_published.pressed.connect(
|
||||
self._on_publish_cancel_pressed
|
||||
)
|
||||
|
||||
# Store attributes
|
||||
self._published_checkbox = published_checkbox
|
||||
|
|
@ -211,18 +269,29 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
self._publish_files_model = publish_files_model
|
||||
self._publish_proxy_model = publish_proxy_model
|
||||
|
||||
self._btns_widget = btns_widget
|
||||
self._publish_context_overlay = publish_context_overlay
|
||||
|
||||
self._workarea_btns_widget = workarea_btns_widget
|
||||
self._publish_btns_widget = publish_btns_widget
|
||||
self._btn_open = btn_open
|
||||
self._btn_browse = btn_browse
|
||||
self._btn_save = btn_save
|
||||
self._btn_view_published = btn_view_published
|
||||
|
||||
self._btn_save_as_published = btn_save_as_published
|
||||
self._btn_change_context = btn_change_context
|
||||
self._btn_select_context_published = btn_select_context_published
|
||||
self._btn_cancel_published = btn_cancel_published
|
||||
|
||||
# Create a proxy widget for files widget
|
||||
self.setFocusProxy(btn_open)
|
||||
|
||||
# Hide publish files widgets
|
||||
publish_files_view.setVisible(False)
|
||||
btn_view_published.setVisible(False)
|
||||
publish_btns_widget.setVisible(False)
|
||||
btn_select_context_published.setVisible(False)
|
||||
btn_cancel_published.setVisible(False)
|
||||
|
||||
self._publish_context_select_mode = False
|
||||
|
||||
@property
|
||||
def published_enabled(self):
|
||||
|
|
@ -232,12 +301,10 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
published_enabled = self.published_enabled
|
||||
|
||||
self._workarea_files_view.setVisible(not published_enabled)
|
||||
self._btn_open.setVisible(not published_enabled)
|
||||
self._btn_browse.setVisible(not published_enabled)
|
||||
self._btn_save.setVisible(not published_enabled)
|
||||
self._workarea_btns_widget.setVisible(not published_enabled)
|
||||
|
||||
self._publish_files_view.setVisible(published_enabled)
|
||||
self._btn_view_published.setVisible(published_enabled)
|
||||
self._publish_btns_widget.setVisible(published_enabled)
|
||||
|
||||
self._update_filtering()
|
||||
self._update_asset_task()
|
||||
|
|
@ -258,6 +325,9 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
|
||||
def set_save_enabled(self, enabled):
|
||||
self._btn_save.setEnabled(enabled)
|
||||
if not enabled and self._published_checkbox.isChecked():
|
||||
self._published_checkbox.setChecked(False)
|
||||
self._published_checkbox.setVisible(enabled)
|
||||
|
||||
def set_asset_task(self, asset_id, task_name, task_type):
|
||||
if asset_id != self._asset_id:
|
||||
|
|
@ -268,12 +338,14 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
self._update_asset_task()
|
||||
|
||||
def _update_asset_task(self):
|
||||
if self.published_enabled:
|
||||
if self.published_enabled and not self._publish_context_select_mode:
|
||||
self._publish_files_model.set_context(
|
||||
self._asset_id, self._task_name
|
||||
)
|
||||
has_valid_items = self._publish_files_model.has_valid_items()
|
||||
self._btn_view_published.setEnabled(has_valid_items)
|
||||
self._btn_save_as_published.setEnabled(has_valid_items)
|
||||
self._btn_change_context.setEnabled(has_valid_items)
|
||||
|
||||
else:
|
||||
# Define a custom session so we can query the work root
|
||||
# for a "Work area" that is not our current Session.
|
||||
|
|
@ -291,6 +363,13 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
has_valid_items = self._workarea_files_model.has_valid_items()
|
||||
self._btn_browse.setEnabled(has_valid_items)
|
||||
self._btn_open.setEnabled(has_valid_items)
|
||||
|
||||
if self._publish_context_select_mode:
|
||||
self._btn_select_context_published.setEnabled(
|
||||
bool(self._asset_id) and bool(self._task_name)
|
||||
)
|
||||
return
|
||||
|
||||
# Manually trigger file selection
|
||||
if not has_valid_items:
|
||||
self.on_file_select()
|
||||
|
|
@ -400,11 +479,18 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
"""
|
||||
session = self._get_session()
|
||||
|
||||
if self.published_enabled:
|
||||
filepath = self._get_selected_filepath()
|
||||
extensions = [os.path.splitext(filepath)[1]]
|
||||
else:
|
||||
extensions = self.host.file_extensions()
|
||||
|
||||
window = SaveAsDialog(
|
||||
parent=self,
|
||||
root=self._workfiles_root,
|
||||
anatomy=self.anatomy,
|
||||
template_key=self.template_key,
|
||||
extensions=extensions,
|
||||
session=session
|
||||
)
|
||||
window.exec_()
|
||||
|
|
@ -462,10 +548,15 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
if work_file:
|
||||
self.open_file(work_file)
|
||||
|
||||
def on_save_as_pressed(self):
|
||||
def _on_save_as_pressed(self):
|
||||
self._save_as_with_dialog()
|
||||
|
||||
def _save_as_with_dialog(self):
|
||||
work_filename = self.get_filename()
|
||||
if not work_filename:
|
||||
return
|
||||
return None
|
||||
|
||||
src_path = self._get_selected_filepath()
|
||||
|
||||
# Trigger before save event
|
||||
emit_event(
|
||||
|
|
@ -486,13 +577,20 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
log.debug("Initializing Work Directory: %s", self._workfiles_root)
|
||||
os.makedirs(self._workfiles_root)
|
||||
|
||||
# Update session if context has changed
|
||||
self._enter_session()
|
||||
# Prepare full path to workfile and save it
|
||||
filepath = os.path.join(
|
||||
os.path.normpath(self._workfiles_root), work_filename
|
||||
)
|
||||
self.host.save_file(filepath)
|
||||
|
||||
# Update session if context has changed
|
||||
self._enter_session()
|
||||
|
||||
if not self.published_enabled:
|
||||
self.host.save_file(filepath)
|
||||
else:
|
||||
shutil.copy(src_path, filepath)
|
||||
self.host.open_file(filepath)
|
||||
|
||||
# Create extra folders
|
||||
create_workdir_extra_folders(
|
||||
self._workdir_path,
|
||||
|
|
@ -510,17 +608,55 @@ class FilesWidget(QtWidgets.QWidget):
|
|||
|
||||
self.workfile_created.emit(filepath)
|
||||
# Refresh files model
|
||||
self.refresh()
|
||||
if self.published_enabled:
|
||||
self._published_checkbox.setChecked(False)
|
||||
else:
|
||||
self.refresh()
|
||||
return filepath
|
||||
|
||||
def _on_view_published_pressed(self):
|
||||
filepath = self._get_selected_filepath()
|
||||
if not filepath or not os.path.exists(filepath):
|
||||
return
|
||||
item = self._temp_publish_files.add_file(filepath)
|
||||
self.host.open_file(item.filepath)
|
||||
self.publish_file_viewed.emit()
|
||||
# Change state back to workarea
|
||||
self._published_checkbox.setChecked(False)
|
||||
def _on_published_save_as_pressed(self):
|
||||
self._save_as_with_dialog()
|
||||
|
||||
def _set_publish_context_select_mode(self, enabled):
|
||||
self._publish_context_select_mode = enabled
|
||||
|
||||
# Show buttons related to context selection
|
||||
self._publish_context_overlay.setVisible(enabled)
|
||||
self._btn_cancel_published.setVisible(enabled)
|
||||
self._btn_select_context_published.setVisible(enabled)
|
||||
# Change enabled state based on select context
|
||||
self._btn_select_context_published.setEnabled(
|
||||
bool(self._asset_id) and bool(self._task_name)
|
||||
)
|
||||
|
||||
self._btn_save_as_published.setVisible(not enabled)
|
||||
self._btn_change_context.setVisible(not enabled)
|
||||
|
||||
# Change views and disable workarea view if enabled
|
||||
self._workarea_files_view.setEnabled(not enabled)
|
||||
if self.published_enabled:
|
||||
self._workarea_files_view.setVisible(enabled)
|
||||
self._publish_files_view.setVisible(not enabled)
|
||||
else:
|
||||
self._workarea_files_view.setVisible(True)
|
||||
self._publish_files_view.setVisible(False)
|
||||
|
||||
# Disable filter widgets
|
||||
self._published_checkbox.setEnabled(not enabled)
|
||||
self._filter_input.setEnabled(not enabled)
|
||||
|
||||
def _on_publish_change_context_pressed(self):
|
||||
self._set_publish_context_select_mode(True)
|
||||
|
||||
def _on_publish_select_context_pressed(self):
|
||||
result = self._save_as_with_dialog()
|
||||
if result is not None:
|
||||
self._set_publish_context_select_mode(False)
|
||||
self._update_asset_task()
|
||||
|
||||
def _on_publish_cancel_pressed(self):
|
||||
self._set_publish_context_select_mode(False)
|
||||
self._update_asset_task()
|
||||
|
||||
def on_file_select(self):
|
||||
self.file_selected.emit(self._get_selected_filepath())
|
||||
|
|
|
|||
|
|
@ -1,272 +0,0 @@
|
|||
import os
|
||||
import shutil
|
||||
import uuid
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
import contextlib
|
||||
|
||||
import appdirs
|
||||
|
||||
|
||||
class TempPublishFilesItem(object):
|
||||
"""Object representing copied workfile in app temp folder.
|
||||
|
||||
Args:
|
||||
item_id (str): Id of item used as subfolder.
|
||||
data (dict): Metadata about temp files.
|
||||
directory (str): Path to directory where files are copied to.
|
||||
"""
|
||||
|
||||
def __init__(self, item_id, data, directory):
|
||||
self._id = item_id
|
||||
self._directory = directory
|
||||
self._filepath = os.path.join(directory, data["filename"])
|
||||
|
||||
@property
|
||||
def directory(self):
|
||||
return self._directory
|
||||
|
||||
@property
|
||||
def filepath(self):
|
||||
return self._filepath
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self._id
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
if os.path.exists(self.filepath):
|
||||
s = os.stat(self.filepath)
|
||||
return s.st_size
|
||||
return 0
|
||||
|
||||
|
||||
class TempPublishFiles(object):
|
||||
"""Directory where published workfiles are copied when opened.
|
||||
|
||||
Directory is located in appdirs on the machine. Folder contains file
|
||||
with metadata about stored files. Each item in metadata has id, filename
|
||||
and expiration time. When expiration time is higher then current time the
|
||||
item is removed from metadata and it's files are deleted. Files of items
|
||||
are stored in subfolder named by item's id.
|
||||
|
||||
Metadata file can be in theory opened and modified by multiple processes,
|
||||
threads at one time. For those cases is created simple lock file which
|
||||
is created before modification begins and is removed when modification
|
||||
ends. Existence of the file means that it should not be modified by
|
||||
any other process at the same time.
|
||||
|
||||
Metadata example:
|
||||
```
|
||||
{
|
||||
"96050b4a-8974-4fca-8179-7c446c478d54": {
|
||||
"created": 1647880725.555,
|
||||
"expiration": 1647884325.555,
|
||||
"filename": "cg_pigeon_workfileModeling_v025.ma"
|
||||
},
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
## Why is this needed
|
||||
Combination of more issues. Temp files are not automatically removed by
|
||||
OS on windows so using tempfiles in TEMP would lead to kill disk space of
|
||||
machine. There are also cases when someone wants to open multiple files
|
||||
in short period of time and want to manually remove those files so keeping
|
||||
track of temporary copied files in pre-defined structure is needed.
|
||||
"""
|
||||
minute_in_seconds = 60
|
||||
hour_in_seconds = 60 * minute_in_seconds
|
||||
day_in_seconds = 24 * hour_in_seconds
|
||||
|
||||
def __init__(self):
|
||||
root_dir = appdirs.user_data_dir(
|
||||
"published_workfiles_temp", "openpype"
|
||||
)
|
||||
if not os.path.exists(root_dir):
|
||||
os.makedirs(root_dir)
|
||||
|
||||
metadata_path = os.path.join(root_dir, "metadata.json")
|
||||
lock_path = os.path.join(root_dir, "lock.json")
|
||||
|
||||
self._root_dir = root_dir
|
||||
self._metadata_path = metadata_path
|
||||
self._lock_path = lock_path
|
||||
self._log = None
|
||||
|
||||
@property
|
||||
def log(self):
|
||||
if self._log is None:
|
||||
self._log = logging.getLogger(self.__class__.__name__)
|
||||
return self._log
|
||||
|
||||
@property
|
||||
def life_time(self):
|
||||
"""How long will be new item kept in temp in seconds.
|
||||
|
||||
Returns:
|
||||
int: Lifetime of temp item.
|
||||
"""
|
||||
return int(self.hour_in_seconds)
|
||||
|
||||
@property
|
||||
def size(self):
|
||||
"""File size of existing items."""
|
||||
size = 0
|
||||
for item in self.get_items():
|
||||
size += item.size
|
||||
return size
|
||||
|
||||
def add_file(self, src_path):
|
||||
"""Add workfile to temp directory.
|
||||
|
||||
This will create new item and source path is copied to it's directory.
|
||||
"""
|
||||
filename = os.path.basename(src_path)
|
||||
|
||||
item_id = str(uuid.uuid4())
|
||||
dst_dirpath = os.path.join(self._root_dir, item_id)
|
||||
if not os.path.exists(dst_dirpath):
|
||||
os.makedirs(dst_dirpath)
|
||||
|
||||
dst_path = os.path.join(dst_dirpath, filename)
|
||||
shutil.copy(src_path, dst_path)
|
||||
|
||||
now = time.time()
|
||||
item_data = {
|
||||
"filename": filename,
|
||||
"expiration": now + self.life_time,
|
||||
"created": now
|
||||
}
|
||||
with self._modify_data() as data:
|
||||
data[item_id] = item_data
|
||||
|
||||
return TempPublishFilesItem(item_id, item_data, dst_dirpath)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _modify_data(self):
|
||||
"""Create lock file when data in metadata file are modified."""
|
||||
start_time = time.time()
|
||||
timeout = 3
|
||||
while os.path.exists(self._lock_path):
|
||||
time.sleep(0.01)
|
||||
if start_time > timeout:
|
||||
self.log.warning((
|
||||
"Waited for {} seconds to free lock file. Overriding lock."
|
||||
).format(timeout))
|
||||
|
||||
with open(self._lock_path, "w") as stream:
|
||||
json.dump({"pid": os.getpid()}, stream)
|
||||
|
||||
try:
|
||||
data = self._get_data()
|
||||
yield data
|
||||
with open(self._metadata_path, "w") as stream:
|
||||
json.dump(data, stream)
|
||||
|
||||
finally:
|
||||
os.remove(self._lock_path)
|
||||
|
||||
def _get_data(self):
|
||||
output = {}
|
||||
if not os.path.exists(self._metadata_path):
|
||||
return output
|
||||
|
||||
try:
|
||||
with open(self._metadata_path, "r") as stream:
|
||||
output = json.load(stream)
|
||||
except Exception:
|
||||
self.log.warning("Failed to read metadata file.", exc_info=True)
|
||||
return output
|
||||
|
||||
def cleanup(self, check_expiration=True):
|
||||
"""Cleanup files based on metadata.
|
||||
|
||||
Items that passed expiration are removed when this is called. Or all
|
||||
files are removed when `check_expiration` is set to False.
|
||||
|
||||
Args:
|
||||
check_expiration (bool): All items and files are removed when set
|
||||
to True.
|
||||
"""
|
||||
data = self._get_data()
|
||||
now = time.time()
|
||||
remove_ids = set()
|
||||
all_ids = set()
|
||||
for item_id, item_data in data.items():
|
||||
all_ids.add(item_id)
|
||||
if check_expiration and now < item_data["expiration"]:
|
||||
continue
|
||||
|
||||
remove_ids.add(item_id)
|
||||
|
||||
for item_id in remove_ids:
|
||||
try:
|
||||
self.remove_id(item_id)
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"Failed to remove temp publish item \"{}\"".format(
|
||||
item_id
|
||||
),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
# Remove unknown folders/files
|
||||
for filename in os.listdir(self._root_dir):
|
||||
if filename in all_ids:
|
||||
continue
|
||||
|
||||
full_path = os.path.join(self._root_dir, filename)
|
||||
if full_path in (self._metadata_path, self._lock_path):
|
||||
continue
|
||||
|
||||
try:
|
||||
shutil.rmtree(full_path)
|
||||
except Exception:
|
||||
self.log.warning(
|
||||
"Couldn't remove arbitrary path \"{}\"".format(full_path),
|
||||
exc_info=True
|
||||
)
|
||||
|
||||
def clear(self):
|
||||
self.cleanup(False)
|
||||
|
||||
def get_items(self):
|
||||
"""Receive all items from metadata file.
|
||||
|
||||
Returns:
|
||||
list<TempPublishFilesItem>: Info about each item in metadata.
|
||||
"""
|
||||
output = []
|
||||
data = self._get_data()
|
||||
for item_id, item_data in data.items():
|
||||
item_path = os.path.join(self._root_dir, item_id)
|
||||
output.append(TempPublishFilesItem(item_id, item_data, item_path))
|
||||
return output
|
||||
|
||||
def remove_id(self, item_id):
|
||||
"""Remove files of item and then remove the item from metadata."""
|
||||
filepath = os.path.join(self._root_dir, item_id)
|
||||
if os.path.exists(filepath):
|
||||
shutil.rmtree(filepath)
|
||||
|
||||
with self._modify_data() as data:
|
||||
data.pop(item_id, None)
|
||||
|
||||
|
||||
def file_size_to_string(file_size):
|
||||
size = 0
|
||||
size_ending_mapping = {
|
||||
"KB": 1024 ** 1,
|
||||
"MB": 1024 ** 2,
|
||||
"GB": 1024 ** 3
|
||||
}
|
||||
ending = "B"
|
||||
for _ending, _size in size_ending_mapping.items():
|
||||
if file_size < _size:
|
||||
break
|
||||
size = file_size / _size
|
||||
ending = _ending
|
||||
return "{:.2f} {}".format(size, ending)
|
||||
|
|
@ -193,7 +193,9 @@ class SaveAsDialog(QtWidgets.QDialog):
|
|||
|
||||
"""
|
||||
|
||||
def __init__(self, parent, root, anatomy, template_key, session=None):
|
||||
def __init__(
|
||||
self, parent, root, anatomy, template_key, extensions, session=None
|
||||
):
|
||||
super(SaveAsDialog, self).__init__(parent=parent)
|
||||
self.setWindowFlags(self.windowFlags() | QtCore.Qt.FramelessWindowHint)
|
||||
|
||||
|
|
@ -201,6 +203,7 @@ class SaveAsDialog(QtWidgets.QDialog):
|
|||
self.host = api.registered_host()
|
||||
self.root = root
|
||||
self.work_file = None
|
||||
self._extensions = extensions
|
||||
|
||||
if not session:
|
||||
# Fallback to active session
|
||||
|
|
@ -257,7 +260,7 @@ class SaveAsDialog(QtWidgets.QDialog):
|
|||
# Add styled delegate to use stylesheets
|
||||
ext_delegate = QtWidgets.QStyledItemDelegate()
|
||||
ext_combo.setItemDelegate(ext_delegate)
|
||||
ext_combo.addItems(self.host.file_extensions())
|
||||
ext_combo.addItems(self._extensions)
|
||||
|
||||
# Build inputs
|
||||
inputs_layout = QtWidgets.QFormLayout(inputs_widget)
|
||||
|
|
@ -336,7 +339,7 @@ class SaveAsDialog(QtWidgets.QDialog):
|
|||
|
||||
def get_existing_comments(self):
|
||||
matcher = CommentMatcher(self.anatomy, self.template_key, self.data)
|
||||
host_extensions = set(self.host.file_extensions())
|
||||
host_extensions = set(self._extensions)
|
||||
comments = set()
|
||||
if os.path.isdir(self.root):
|
||||
for fname in os.listdir(self.root):
|
||||
|
|
@ -392,7 +395,7 @@ class SaveAsDialog(QtWidgets.QDialog):
|
|||
return anatomy_filled[self.template_key]["file"]
|
||||
|
||||
def refresh(self):
|
||||
extensions = self.host.file_extensions()
|
||||
extensions = list(self._extensions)
|
||||
extension = self.data["ext"]
|
||||
if extension is None:
|
||||
# Define saving file extension
|
||||
|
|
|
|||
|
|
@ -14,7 +14,22 @@ from openpype.tools.utils.assets_widget import SingleSelectAssetsWidget
|
|||
from openpype.tools.utils.tasks_widget import TasksWidget
|
||||
|
||||
from .files_widget import FilesWidget
|
||||
from .lib import TempPublishFiles, file_size_to_string
|
||||
|
||||
|
||||
def file_size_to_string(file_size):
|
||||
size = 0
|
||||
size_ending_mapping = {
|
||||
"KB": 1024 ** 1,
|
||||
"MB": 1024 ** 2,
|
||||
"GB": 1024 ** 3
|
||||
}
|
||||
ending = "B"
|
||||
for _ending, _size in size_ending_mapping.items():
|
||||
if file_size < _size:
|
||||
break
|
||||
size = file_size / _size
|
||||
ending = _ending
|
||||
return "{:.2f} {}".format(size, ending)
|
||||
|
||||
|
||||
class SidePanelWidget(QtWidgets.QWidget):
|
||||
|
|
@ -44,67 +59,25 @@ class SidePanelWidget(QtWidgets.QWidget):
|
|||
btn_note_save, 0, alignment=QtCore.Qt.AlignRight
|
||||
)
|
||||
|
||||
publish_temp_widget = QtWidgets.QWidget(self)
|
||||
publish_temp_info_label = QtWidgets.QLabel(
|
||||
self.published_workfile_message.format(
|
||||
file_size_to_string(0)
|
||||
),
|
||||
publish_temp_widget
|
||||
)
|
||||
publish_temp_info_label.setWordWrap(True)
|
||||
|
||||
btn_clear_temp = QtWidgets.QPushButton(
|
||||
"Clear temp", publish_temp_widget
|
||||
)
|
||||
|
||||
publish_temp_layout = QtWidgets.QVBoxLayout(publish_temp_widget)
|
||||
publish_temp_layout.setContentsMargins(0, 0, 0, 0)
|
||||
publish_temp_layout.addWidget(publish_temp_info_label, 0)
|
||||
publish_temp_layout.addWidget(
|
||||
btn_clear_temp, 0, alignment=QtCore.Qt.AlignRight
|
||||
)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.addWidget(details_label, 0)
|
||||
main_layout.addWidget(details_input, 1)
|
||||
main_layout.addWidget(artist_note_widget, 1)
|
||||
main_layout.addWidget(publish_temp_widget, 0)
|
||||
|
||||
note_input.textChanged.connect(self._on_note_change)
|
||||
btn_note_save.clicked.connect(self._on_save_click)
|
||||
btn_clear_temp.clicked.connect(self._on_clear_temp_click)
|
||||
|
||||
self._details_input = details_input
|
||||
self._artist_note_widget = artist_note_widget
|
||||
self._note_input = note_input
|
||||
self._btn_note_save = btn_note_save
|
||||
|
||||
self._publish_temp_info_label = publish_temp_info_label
|
||||
self._publish_temp_widget = publish_temp_widget
|
||||
|
||||
self._orig_note = ""
|
||||
self._workfile_doc = None
|
||||
|
||||
publish_temp_widget.setVisible(False)
|
||||
|
||||
def set_published_visible(self, published_visible):
|
||||
self._artist_note_widget.setVisible(not published_visible)
|
||||
self._publish_temp_widget.setVisible(published_visible)
|
||||
if published_visible:
|
||||
self.refresh_publish_temp_sizes()
|
||||
|
||||
def refresh_publish_temp_sizes(self):
|
||||
temp_publish_files = TempPublishFiles()
|
||||
text = self.published_workfile_message.format(
|
||||
file_size_to_string(temp_publish_files.size)
|
||||
)
|
||||
self._publish_temp_info_label.setText(text)
|
||||
|
||||
def _on_clear_temp_click(self):
|
||||
temp_publish_files = TempPublishFiles()
|
||||
temp_publish_files.clear()
|
||||
self.refresh_publish_temp_sizes()
|
||||
|
||||
def _on_note_change(self):
|
||||
text = self._note_input.toPlainText()
|
||||
|
|
@ -225,9 +198,6 @@ class Window(QtWidgets.QMainWindow):
|
|||
files_widget.file_selected.connect(self.on_file_select)
|
||||
files_widget.workfile_created.connect(self.on_workfile_create)
|
||||
files_widget.file_opened.connect(self._on_file_opened)
|
||||
files_widget.publish_file_viewed.connect(
|
||||
self._on_publish_file_viewed
|
||||
)
|
||||
files_widget.published_visible_changed.connect(
|
||||
self._on_published_change
|
||||
)
|
||||
|
|
@ -292,9 +262,6 @@ class Window(QtWidgets.QMainWindow):
|
|||
def _on_file_opened(self):
|
||||
self.close()
|
||||
|
||||
def _on_publish_file_viewed(self):
|
||||
self.side_panel.refresh_publish_temp_sizes()
|
||||
|
||||
def _on_published_change(self, visible):
|
||||
self.side_panel.set_published_visible(visible)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue