Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Feature/sync server priority #1444

Merged
merged 16 commits into from
May 4, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 69 additions & 8 deletions openpype/modules/sync_server/sync_server_module.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ class SyncServerModule(PypeModule, ITrayModule):
DEFAULT_SITE = 'studio'
LOCAL_SITE = 'local'
LOG_PROGRESS_SEC = 5 # how often log progress to DB
DEFAULT_PRIORITY = 50 # higher is better, allowed range 1 - 1000

name = "sync_server"
label = "Sync Queue"
Expand Down Expand Up @@ -472,6 +473,7 @@ def tray_init(self):

try:
self.sync_server_thread = SyncServerThread(self)

from .tray.app import SyncServerWindow
self.widget = SyncServerWindow(self)
except ValueError:
Expand Down Expand Up @@ -662,7 +664,7 @@ def get_sync_representations(self, collection, active_site, remote_site):
self.connection.Session["AVALON_PROJECT"] = collection
# retry_cnt - number of attempts to sync specific file before giving up
retries_arr = self._get_retries_arr(collection)
query = {
match = {
"type": "representation",
"$or": [
{"$and": [
Expand Down Expand Up @@ -700,10 +702,47 @@ def get_sync_representations(self, collection, active_site, remote_site):
]}
]
}

aggr = [
{"$match": match},
{'$unwind': '$files'},
{'$addFields': {
'order_remote': {
'$filter': {'input': '$files.sites', 'as': 'p',
'cond': {'$eq': ['$$p.name', remote_site]}
}},
'order_local': {
'$filter': {'input': '$files.sites', 'as': 'p',
'cond': {'$eq': ['$$p.name', active_site]}
}},
}},
{'$addFields': {
'priority': {
'$cond': [
{'$size': '$order_local.priority'},
{'$first': '$order_local.priority'},
{'$cond': [
{'$size': '$order_remote.priority'},
{'$first': '$order_remote.priority'},
self.DEFAULT_PRIORITY]}
]
},
}},
{'$group': {
'_id': '$_id',
# pass through context - same for representation
'context': {'$addToSet': '$context'},
'data': {'$addToSet': '$data'},
# pass through files as a list
'files': {'$addToSet': '$files'},
'priority': {'$max': "$priority"},
}},
{"$sort": {'priority': -1, '_id': 1}},
]
log.debug("active_site:{} - remote_site:{}".format(active_site,
remote_site))
log.debug("query: {}".format(query))
representations = self.connection.find(query)
log.debug("query: {}".format(aggr))
representations = self.connection.aggregate(aggr)

return representations

Expand Down Expand Up @@ -749,7 +788,7 @@ def check_status(self, file, local_site, remote_site, config_preset):
return SyncStatus.DO_NOTHING

def update_db(self, collection, new_file_id, file, representation,
site, error=None, progress=None):
site, error=None, progress=None, priority=None):
"""
Update 'provider' portion of records in DB with success (file_id)
or error (exception)
Expand All @@ -763,12 +802,16 @@ def update_db(self, collection, new_file_id, file, representation,
site (string): label ('gdrive', 'S3')
error (string): exception message
progress (float): 0-1 of progress of upload/download
priority (int): 0-100 set priority

Returns:
None
"""
representation_id = representation.get("_id")
file_id = file.get("_id")
file_id = None
if file:
file_id = file.get("_id")

query = {
"_id": representation_id
}
Expand All @@ -780,16 +823,19 @@ def update_db(self, collection, new_file_id, file, representation,
update["$unset"] = self._get_error_dict("", "", "")
elif progress is not None:
update["$set"] = self._get_progress_dict(progress)
elif priority is not None:
update["$set"] = self._get_priority_dict(priority, file_id)
else:
tries = self._get_tries_count(file, site)
tries += 1

update["$set"] = self._get_error_dict(error, tries)

arr_filter = [
{'s.name': site},
{'f._id': ObjectId(file_id)}
{'s.name': site}
]
if file_id:
arr_filter.append({'f._id': ObjectId(file_id)})

self.connection.database[collection].update_one(
query,
Expand All @@ -798,7 +844,7 @@ def update_db(self, collection, new_file_id, file, representation,
array_filters=arr_filter
)

if progress is not None:
if progress is not None or priority is not None:
return

status = 'failed'
Expand Down Expand Up @@ -1192,6 +1238,21 @@ def _get_progress_dict(self, progress):
val = {"files.$[f].sites.$[s].progress": progress}
return val

def _get_priority_dict(self, priority, file_id):
"""
Provide priority metadata to be stored in Db.
Used during upload/download for GUI to show.
Args:
priority: (int) - priority for file(s)
Returns:
(dictionary)
"""
if file_id:
str_key = "files.$[f].sites.$[s].priority"
else:
str_key = "files.$[].sites.$[s].priority"
return {str_key: int(priority)}

def _get_retries_arr(self, project_name):
"""
Returns array with allowed values in 'tries' field. If repre
Expand Down
18 changes: 18 additions & 0 deletions openpype/modules/sync_server/tray/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,26 @@ def __init__(self, sync_server, parent=None):
self.projects.current_project))

self.pause_btn.clicked.connect(self._pause)
self.pause_btn.setAutoDefault(False)
self.pause_btn.setDefault(False)
repres.message_generated.connect(self._update_message)

self.representationWidget = repres

def showEvent(self, event):
self.representationWidget.model.set_project(
self.projects.current_project)
self._set_running(True)
super().showEvent(event)

def closeEvent(self, event):
self._set_running(False)
super().closeEvent(event)

def _set_running(self, running):
self.representationWidget.model.is_running = running
self.representationWidget.model.timer.setInterval(0)

def _pause(self):
if self.sync_server.is_paused():
self.sync_server.unpause_server()
Expand Down
116 changes: 116 additions & 0 deletions openpype/modules/sync_server/tray/delegates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import os
from Qt import QtCore, QtWidgets, QtGui

from openpype.lib import PypeLogger
from openpype.modules.sync_server.tray import lib

log = PypeLogger().get_logger("SyncServer")


class PriorityDelegate(QtWidgets.QStyledItemDelegate):
"""Creates editable line edit to set priority on representation"""
def paint(self, painter, option, index):
super(PriorityDelegate, self).paint(painter, option, index)

if option.widget.selectionModel().isSelected(index) or \
option.state & QtWidgets.QStyle.State_MouseOver:
edit_icon = index.data(lib.EditIconRole)
if not edit_icon:
return

state = QtGui.QIcon.On
mode = QtGui.QIcon.Selected

icon_side = 16
icon_rect = QtCore.QRect(
option.rect.left() + option.rect.width() - icon_side - 4,
option.rect.top() + ((option.rect.height() - icon_side) / 2),
icon_side,
icon_side
)

edit_icon.paint(
painter, icon_rect,
QtCore.Qt.AlignRight, mode, state
)

def createEditor(self, parent, option, index):
editor = PriorityLineEdit(
parent,
option.widget.selectionModel().selectedRows())
editor.setFocus(True)
return editor

def setModelData(self, editor, model, index):
for index in editor.selected_idxs:
try:
val = int(editor.text())
except ValueError:
val = model.sync_server.DEFAULT_PRIORITY
model.set_priority_data(index, val)


class PriorityLineEdit(QtWidgets.QLineEdit):
"""Special LineEdit to consume Enter and store selected indexes"""
def __init__(self, parent=None, selected_idxs=None):
self.selected_idxs = selected_idxs
super(PriorityLineEdit, self).__init__(parent)

def keyPressEvent(self, event):
result = super(PriorityLineEdit, self).keyPressEvent(event)
if (
event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter)
):
return event.accept()

return result


class ImageDelegate(QtWidgets.QStyledItemDelegate):
"""
Prints icon of site and progress of synchronization
"""

def __init__(self, parent=None):
super(ImageDelegate, self).__init__(parent)
self.icons = {}

def paint(self, painter, option, index):
super(ImageDelegate, self).paint(painter, option, index)
option = QtWidgets.QStyleOptionViewItem(option)
option.showDecorationSelected = True

provider = index.data(lib.ProviderRole)
value = index.data(lib.ProgressRole)
date_value = index.data(lib.DateRole)
is_failed = index.data(lib.FailedRole)

if not self.icons.get(provider):
resource_path = os.path.dirname(__file__)
resource_path = os.path.join(resource_path, "..",
"providers", "resources")
pix_url = "{}/{}.png".format(resource_path, provider)
pixmap = QtGui.QPixmap(pix_url)
self.icons[provider] = pixmap
else:
pixmap = self.icons[provider]

padding = 10
point = QtCore.QPoint(option.rect.x() + padding,
option.rect.y() +
(option.rect.height() - pixmap.height()) / 2)
painter.drawPixmap(point, pixmap)

overlay_rect = option.rect.translated(0, 0)
overlay_rect.setHeight(overlay_rect.height() * (1.0 - float(value)))
painter.fillRect(overlay_rect,
QtGui.QBrush(QtGui.QColor(0, 0, 0, 100)))
text_rect = option.rect.translated(10, 0)
painter.drawText(text_rect,
QtCore.Qt.AlignCenter,
date_value)

if is_failed:
overlay_rect = option.rect.translated(0, 0)
painter.fillRect(overlay_rect,
QtGui.QBrush(QtGui.QColor(255, 0, 0, 35)))
1 change: 1 addition & 0 deletions openpype/modules/sync_server/tray/lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
FailedRole = QtCore.Qt.UserRole + 8
HeaderNameRole = QtCore.Qt.UserRole + 10
FullItemRole = QtCore.Qt.UserRole + 12
EditIconRole = QtCore.Qt.UserRole + 14


@six.add_metaclass(abc.ABCMeta)
Expand Down
Loading