mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #1115 from pypeclub/sync_server_fix_local_drive
Sync server fix local drive
This commit is contained in:
commit
80857e83c0
13 changed files with 645 additions and 326 deletions
|
|
@ -77,7 +77,7 @@ class Anatomy:
|
|||
root_key_regex = re.compile(r"{(root?[^}]+)}")
|
||||
root_name_regex = re.compile(r"root\[([^]]+)\]")
|
||||
|
||||
def __init__(self, project_name=None):
|
||||
def __init__(self, project_name=None, site_name=None):
|
||||
if not project_name:
|
||||
project_name = os.environ.get("AVALON_PROJECT")
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ class Anatomy:
|
|||
|
||||
self.project_name = project_name
|
||||
|
||||
self._data = get_anatomy_settings(project_name)
|
||||
self._data = get_anatomy_settings(project_name, site_name)
|
||||
|
||||
self._templates_obj = Templates(self)
|
||||
self._roots_obj = Roots(self)
|
||||
|
|
|
|||
|
|
@ -71,3 +71,39 @@ class AbstractProvider(metaclass=ABCMeta):
|
|||
(list)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def create_folder(self, folder_path):
|
||||
"""
|
||||
Create all nonexistent folders and subfolders in 'path'.
|
||||
|
||||
Args:
|
||||
path (string): absolute path
|
||||
|
||||
Returns:
|
||||
(string) folder id of lowest subfolder from 'path'
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_tree(self):
|
||||
"""
|
||||
Creates folder structure for providers which do not provide
|
||||
tree folder structure (GDrive has no accessible tree structure,
|
||||
only parents and their parents)
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def resolve_path(self, path, root_config, anatomy=None):
|
||||
"""
|
||||
Replaces root placeholders with appropriate real value from
|
||||
'root_configs' (from Settings or Local Settings) or Anatomy
|
||||
(mainly for 'studio' site)
|
||||
|
||||
Args:
|
||||
path(string): path with '{root[work]}/...'
|
||||
root_config(dict): from Settings or Local Settings
|
||||
anatomy (Anatomy): prepared anatomy object for project
|
||||
"""
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -678,6 +678,16 @@ class GDriveHandler(AbstractProvider):
|
|||
return
|
||||
return provider_presets
|
||||
|
||||
def resolve_path(self, path, root_config, anatomy=None):
|
||||
if not root_config.get("root"):
|
||||
root_config = {"root": root_config}
|
||||
|
||||
try:
|
||||
return path.format(**root_config)
|
||||
except KeyError:
|
||||
msg = "Error in resolving remote root, unknown key"
|
||||
log.error(msg)
|
||||
|
||||
def _handle_q(self, q, trashed=False):
|
||||
""" API list call contain trashed and hidden files/folder by default.
|
||||
Usually we dont want those, must be included in query explicitly.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
from __future__ import print_function
|
||||
import os.path
|
||||
import shutil
|
||||
import threading
|
||||
import time
|
||||
|
||||
from pype.api import Logger
|
||||
from .abstract_provider import AbstractProvider
|
||||
|
|
@ -13,29 +15,37 @@ class LocalDriveHandler(AbstractProvider):
|
|||
def is_active(self):
|
||||
return True
|
||||
|
||||
def upload_file(self, source_path, target_path, overwrite=True):
|
||||
def upload_file(self, source_path, target_path,
|
||||
server, collection, file, representation, site,
|
||||
overwrite=False, direction="Upload"):
|
||||
"""
|
||||
Copies file from 'source_path' to 'target_path'
|
||||
"""
|
||||
if os.path.exists(source_path):
|
||||
if overwrite:
|
||||
shutil.copy(source_path, target_path)
|
||||
else:
|
||||
if os.path.exists(target_path):
|
||||
raise ValueError("File {} exists, set overwrite".
|
||||
format(target_path))
|
||||
if not os.path.isfile(source_path):
|
||||
raise FileNotFoundError("Source file {} doesn't exist."
|
||||
.format(source_path))
|
||||
if overwrite:
|
||||
thread = threading.Thread(target=self._copy,
|
||||
args=(source_path, target_path))
|
||||
thread.start()
|
||||
self._mark_progress(collection, file, representation, server,
|
||||
site, source_path, target_path, direction)
|
||||
else:
|
||||
if os.path.exists(target_path):
|
||||
raise ValueError("File {} exists, set overwrite".
|
||||
format(target_path))
|
||||
|
||||
def download_file(self, source_path, local_path, overwrite=True):
|
||||
return os.path.basename(target_path)
|
||||
|
||||
def download_file(self, source_path, local_path,
|
||||
server, collection, file, representation, site,
|
||||
overwrite=False):
|
||||
"""
|
||||
Download a file form 'source_path' to 'local_path'
|
||||
"""
|
||||
if os.path.exists(source_path):
|
||||
if overwrite:
|
||||
shutil.copy(source_path, local_path)
|
||||
else:
|
||||
if os.path.exists(local_path):
|
||||
raise ValueError("File {} exists, set overwrite".
|
||||
format(local_path))
|
||||
return self.upload_file(source_path, local_path,
|
||||
server, collection, file, representation, site,
|
||||
overwrite, direction="Download")
|
||||
|
||||
def delete_file(self, path):
|
||||
"""
|
||||
|
|
@ -57,3 +67,69 @@ class LocalDriveHandler(AbstractProvider):
|
|||
lst.append(os.path.join(dir_path, name))
|
||||
|
||||
return lst
|
||||
|
||||
def create_folder(self, folder_path):
|
||||
"""
|
||||
Creates 'folder_path' on local system
|
||||
|
||||
Args:
|
||||
folder_path (string): absolute path on local (and mounted) disk
|
||||
|
||||
Returns:
|
||||
(string) - sends back folder_path to denote folder(s) was
|
||||
created
|
||||
"""
|
||||
os.makedirs(folder_path, exist_ok=True)
|
||||
return folder_path
|
||||
|
||||
def get_tree(self):
|
||||
return
|
||||
|
||||
def resolve_path(self, path, root_config, anatomy=None):
|
||||
if root_config and not root_config.get("root"):
|
||||
root_config = {"root": root_config}
|
||||
|
||||
try:
|
||||
if not root_config:
|
||||
raise KeyError
|
||||
|
||||
path = path.format(**root_config)
|
||||
except KeyError:
|
||||
try:
|
||||
path = anatomy.fill_root(path)
|
||||
except KeyError:
|
||||
msg = "Error in resolving local root from anatomy"
|
||||
log.error(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
return path
|
||||
|
||||
def _copy(self, source_path, target_path):
|
||||
print("copying {}->{}".format(source_path, target_path))
|
||||
shutil.copy(source_path, target_path)
|
||||
|
||||
def _mark_progress(self, collection, file, representation, server, site,
|
||||
source_path, target_path, direction):
|
||||
"""
|
||||
Updates progress field in DB by values 0-1.
|
||||
|
||||
Compares file sizes of source and target.
|
||||
"""
|
||||
source_file_size = os.path.getsize(source_path)
|
||||
target_file_size = 0
|
||||
last_tick = status_val = None
|
||||
while source_file_size != target_file_size:
|
||||
if not last_tick or \
|
||||
time.time() - last_tick >= server.LOG_PROGRESS_SEC:
|
||||
status_val = target_file_size / source_file_size
|
||||
last_tick = time.time()
|
||||
log.debug(direction + "ed %d%%." % int(status_val * 100))
|
||||
server.update_db(collection=collection,
|
||||
new_file_id=None,
|
||||
file=file,
|
||||
representation=representation,
|
||||
site=site,
|
||||
progress=status_val
|
||||
)
|
||||
target_file_size = os.path.getsize(target_path)
|
||||
time.sleep(0.5)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
from pype.api import (
|
||||
Anatomy,
|
||||
get_project_settings,
|
||||
get_current_project_settings)
|
||||
get_local_site_id)
|
||||
|
||||
import threading
|
||||
import concurrent.futures
|
||||
|
|
@ -97,6 +97,7 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
# set 0 to no limit
|
||||
REPRESENTATION_LIMIT = 100
|
||||
DEFAULT_SITE = 'studio'
|
||||
LOCAL_SITE = 'local'
|
||||
LOG_PROGRESS_SEC = 5 # how often log progress to DB
|
||||
|
||||
name = "sync_server"
|
||||
|
|
@ -110,8 +111,7 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
Sets 'enabled' according to global settings for the module.
|
||||
Shouldnt be doing any initialization, thats a job for 'tray_init'
|
||||
"""
|
||||
sync_server_settings = module_settings[self.name]
|
||||
self.enabled = sync_server_settings["enabled"]
|
||||
self.enabled = module_settings[self.name]["enabled"]
|
||||
if asyncio is None:
|
||||
raise AssertionError(
|
||||
"SyncServer module requires Python 3.5 or higher."
|
||||
|
|
@ -119,7 +119,8 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
# some parts of code need to run sequentially, not in async
|
||||
self.lock = None
|
||||
self.connection = None # connection to avalon DB to update state
|
||||
self.presets = None # settings for all enabled projects for sync
|
||||
# settings for all enabled projects for sync
|
||||
self.sync_project_settings = None
|
||||
self.sync_server_thread = None # asyncio requires new thread
|
||||
|
||||
self.action_show_widget = None
|
||||
|
|
@ -128,7 +129,7 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
self._paused_representations = set()
|
||||
self._anatomies = {}
|
||||
|
||||
# public facing API
|
||||
""" Start of Public API """
|
||||
def add_site(self, collection, representation_id, site_name=None):
|
||||
"""
|
||||
Adds new site to representation to be synced.
|
||||
|
|
@ -146,7 +147,7 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
Returns:
|
||||
throws ValueError if any issue
|
||||
"""
|
||||
if not self.get_synced_preset(collection):
|
||||
if not self.get_sync_project_setting(collection):
|
||||
raise ValueError("Project not configured")
|
||||
|
||||
if not site_name:
|
||||
|
|
@ -173,7 +174,7 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
Returns:
|
||||
throws ValueError if any issue
|
||||
"""
|
||||
if not self.get_synced_preset(collection):
|
||||
if not self.get_sync_project_setting(collection):
|
||||
raise ValueError("Project not configured")
|
||||
|
||||
self.reset_provider_for_file(collection,
|
||||
|
|
@ -322,6 +323,115 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
""" Is server paused """
|
||||
return self._paused
|
||||
|
||||
def get_active_sites(self, project_name):
|
||||
"""
|
||||
Returns list of active sites for 'project_name'.
|
||||
|
||||
By default it returns ['studio'], this site is default
|
||||
and always present even if SyncServer is not enabled. (for publish)
|
||||
|
||||
Used mainly for Local settings for user override.
|
||||
|
||||
Args:
|
||||
project_name (string):
|
||||
|
||||
Returns:
|
||||
(list) of strings
|
||||
"""
|
||||
return self.get_active_sites_from_settings(
|
||||
get_project_settings(project_name))
|
||||
|
||||
def get_active_sites_from_settings(self, settings):
|
||||
"""
|
||||
List available active sites from incoming 'settings'. Used for
|
||||
returning 'default' values for Local Settings
|
||||
|
||||
Args:
|
||||
settings (dict): full settings (global + project)
|
||||
Returns:
|
||||
(list) of strings
|
||||
"""
|
||||
sync_settings = self._parse_sync_settings_from_settings(settings)
|
||||
|
||||
return self._get_active_sites_from_settings(sync_settings)
|
||||
|
||||
def get_active_site(self, project_name):
|
||||
"""
|
||||
Returns active (mine) site for 'project_name' from settings
|
||||
|
||||
Returns:
|
||||
(string)
|
||||
"""
|
||||
active_site = self.get_sync_project_setting(
|
||||
project_name)['config']['active_site']
|
||||
if active_site == self.LOCAL_SITE:
|
||||
return get_local_site_id()
|
||||
return active_site
|
||||
|
||||
# remote sites
|
||||
def get_remote_sites(self, project_name):
|
||||
"""
|
||||
Returns all remote sites configured on 'project_name'.
|
||||
|
||||
If 'project_name' is not enabled for syncing returns [].
|
||||
|
||||
Used by Local setting to allow user choose remote site.
|
||||
|
||||
Args:
|
||||
project_name (string):
|
||||
|
||||
Returns:
|
||||
(list) of strings
|
||||
"""
|
||||
return self.get_remote_sites_from_settings(
|
||||
get_project_settings(project_name))
|
||||
|
||||
def get_remote_sites_from_settings(self, settings):
|
||||
"""
|
||||
Get remote sites for returning 'default' values for Local Settings
|
||||
"""
|
||||
sync_settings = self._parse_sync_settings_from_settings(settings)
|
||||
|
||||
return self._get_remote_sites_from_settings(sync_settings)
|
||||
|
||||
def get_remote_site(self, project_name):
|
||||
"""
|
||||
Returns remote (theirs) site for 'project_name' from settings
|
||||
"""
|
||||
remote_site = self.get_sync_project_setting(
|
||||
project_name)['config']['remote_site']
|
||||
if remote_site == self.LOCAL_SITE:
|
||||
return get_local_site_id()
|
||||
|
||||
return remote_site
|
||||
|
||||
""" End of Public API """
|
||||
|
||||
def get_local_file_path(self, collection, file_path):
|
||||
"""
|
||||
Externalized for app
|
||||
"""
|
||||
local_file_path, _ = self._resolve_paths(file_path, collection)
|
||||
|
||||
return local_file_path
|
||||
|
||||
def _get_remote_sites_from_settings(self, sync_settings):
|
||||
if not self.enabled or not sync_settings['enabled']:
|
||||
return []
|
||||
|
||||
remote_sites = [self.DEFAULT_SITE, self.LOCAL_SITE]
|
||||
if sync_settings:
|
||||
remote_sites.extend(sync_settings.get("sites").keys())
|
||||
|
||||
return list(set(remote_sites))
|
||||
|
||||
def _get_active_sites_from_settings(self, sync_settings):
|
||||
sites = [self.DEFAULT_SITE]
|
||||
if self.enabled and sync_settings['enabled']:
|
||||
sites.append(self.LOCAL_SITE)
|
||||
|
||||
return sites
|
||||
|
||||
def connect_with_modules(self, *_a, **kw):
|
||||
return
|
||||
|
||||
|
|
@ -335,15 +445,14 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
if not self.enabled:
|
||||
return
|
||||
|
||||
self.presets = None
|
||||
self.sync_project_settings = None
|
||||
self.lock = threading.Lock()
|
||||
|
||||
self.connection = AvalonMongoDB()
|
||||
self.connection.install()
|
||||
|
||||
try:
|
||||
self.presets = self.get_synced_presets()
|
||||
self.set_active_sites(self.presets)
|
||||
self.set_sync_project_settings()
|
||||
self.sync_server_thread = SyncServerThread(self)
|
||||
from .tray.app import SyncServerWindow
|
||||
self.widget = SyncServerWindow(self)
|
||||
|
|
@ -355,7 +464,7 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
"There are not set presets for SyncServer OR "
|
||||
"Credentials provided are invalid, "
|
||||
"no syncing possible").
|
||||
format(str(self.presets)), exc_info=True)
|
||||
format(str(self.sync_project_settings)), exc_info=True)
|
||||
self.enabled = False
|
||||
|
||||
def tray_start(self):
|
||||
|
|
@ -369,7 +478,7 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
Returns:
|
||||
None
|
||||
"""
|
||||
if self.presets and self.active_sites:
|
||||
if self.sync_project_settings and self.enabled:
|
||||
self.sync_server_thread.start()
|
||||
else:
|
||||
log.info("No presets or active providers. " +
|
||||
|
|
@ -425,31 +534,48 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
"""
|
||||
return self._anatomies.get('project_name') or Anatomy(project_name)
|
||||
|
||||
def get_synced_presets(self):
|
||||
def set_sync_project_settings(self):
|
||||
"""
|
||||
Collects all projects which have enabled syncing and their settings
|
||||
Returns:
|
||||
(dict): of settings, keys are project names
|
||||
"""
|
||||
if self.presets: # presets set already, do not call again and again
|
||||
return self.presets
|
||||
Set sync_project_settings for all projects (caching)
|
||||
|
||||
sync_presets = {}
|
||||
For performance
|
||||
"""
|
||||
sync_project_settings = {}
|
||||
if not self.connection:
|
||||
self.connection = AvalonMongoDB()
|
||||
self.connection.install()
|
||||
|
||||
for collection in self.connection.database.collection_names(False):
|
||||
sync_settings = self.get_synced_preset(collection)
|
||||
sync_settings = self._parse_sync_settings_from_settings(
|
||||
get_project_settings(collection))
|
||||
if sync_settings:
|
||||
sync_presets[collection] = sync_settings
|
||||
default_sites = self._get_default_site_configs()
|
||||
sync_settings['sites'].update(default_sites)
|
||||
sync_project_settings[collection] = sync_settings
|
||||
|
||||
if not sync_presets:
|
||||
if not sync_project_settings:
|
||||
log.info("No enabled and configured projects for sync.")
|
||||
|
||||
return sync_presets
|
||||
self.sync_project_settings = sync_project_settings
|
||||
|
||||
def get_synced_preset(self, project_name):
|
||||
def get_sync_project_settings(self, refresh=False):
|
||||
"""
|
||||
Collects all projects which have enabled syncing and their settings
|
||||
Args:
|
||||
refresh (bool): refresh presets from settings - used when user
|
||||
changes site in Local Settings or any time up-to-date values
|
||||
are necessary
|
||||
Returns:
|
||||
(dict): of settings, keys are project names
|
||||
{'projectA':{enabled: True, sites:{}...}
|
||||
"""
|
||||
# presets set already, do not call again and again
|
||||
if refresh or not self.sync_project_settings:
|
||||
self.set_sync_project_settings()
|
||||
|
||||
return self.sync_project_settings
|
||||
|
||||
def get_sync_project_setting(self, project_name):
|
||||
""" Handles pulling sync_server's settings for enabled 'project_name'
|
||||
|
||||
Args:
|
||||
|
|
@ -460,83 +586,91 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
"""
|
||||
# presets set already, do not call again and again
|
||||
# self.log.debug("project preset {}".format(self.presets))
|
||||
if self.presets and self.presets.get(project_name):
|
||||
return self.presets.get(project_name)
|
||||
if self.sync_project_settings and \
|
||||
self.sync_project_settings.get(project_name):
|
||||
return self.sync_project_settings.get(project_name)
|
||||
|
||||
settings = get_project_settings(project_name)
|
||||
sync_settings = settings.get("global")["sync_server"]
|
||||
return self._parse_sync_settings_from_settings(settings)
|
||||
|
||||
def site_is_working(self, project_name, site_name):
|
||||
"""
|
||||
Confirm that 'site_name' is configured correctly for 'project_name'
|
||||
Args:
|
||||
project_name(string):
|
||||
site_name(string):
|
||||
Returns
|
||||
(bool)
|
||||
"""
|
||||
if self._get_configured_sites(project_name).get(site_name):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _parse_sync_settings_from_settings(self, settings):
|
||||
""" settings from api.get_project_settings, TOOD rename """
|
||||
sync_settings = settings.get("global").get("sync_server")
|
||||
if not sync_settings:
|
||||
log.info("No project setting for {}, not syncing.".
|
||||
format(project_name))
|
||||
log.info("No project setting not syncing.")
|
||||
return {}
|
||||
if sync_settings.get("enabled"):
|
||||
return sync_settings
|
||||
|
||||
return {}
|
||||
|
||||
def set_active_sites(self, settings):
|
||||
def _get_configured_sites(self, project_name):
|
||||
"""
|
||||
Sets 'self.active_sites' as a dictionary from provided 'settings'
|
||||
|
||||
Format:
|
||||
{ 'project_name' : ('provider_name', 'site_name') }
|
||||
Args:
|
||||
settings (dict): all enabled project sync setting (sites labesl,
|
||||
retries count etc.)
|
||||
"""
|
||||
self.active_sites = {}
|
||||
initiated_handlers = {}
|
||||
for project_name, project_setting in settings.items():
|
||||
for site_name, config in project_setting.get("sites").items():
|
||||
handler = initiated_handlers.get(config["provider"])
|
||||
if not handler:
|
||||
handler = lib.factory.get_provider(config["provider"],
|
||||
site_name,
|
||||
presets=config)
|
||||
initiated_handlers[config["provider"]] = handler
|
||||
|
||||
if handler.is_active():
|
||||
if not self.active_sites.get('project_name'):
|
||||
self.active_sites[project_name] = []
|
||||
|
||||
self.active_sites[project_name].append(
|
||||
(config["provider"], site_name))
|
||||
|
||||
if not self.active_sites:
|
||||
log.info("No sync sites active, no working credentials provided")
|
||||
|
||||
def get_active_sites(self, project_name):
|
||||
"""
|
||||
Returns active sites (provider configured and able to connect) per
|
||||
project.
|
||||
Loops through settings and looks for configured sites and checks
|
||||
its handlers for particular 'project_name'.
|
||||
|
||||
Args:
|
||||
project_name (str): used as a key in dict
|
||||
|
||||
project_setting(dict): dictionary from Settings
|
||||
only_project_name(string, optional): only interested in
|
||||
particular project
|
||||
Returns:
|
||||
(dict):
|
||||
Format:
|
||||
{ 'project_name' : ('provider_name', 'site_name') }
|
||||
(dict of dict)
|
||||
{'ProjectA': {'studio':True, 'gdrive':False}}
|
||||
"""
|
||||
return self.active_sites[project_name]
|
||||
settings = self.get_sync_project_setting(project_name)
|
||||
return self._get_configured_sites_from_setting(settings)
|
||||
|
||||
def get_local_site(self, project_name):
|
||||
"""
|
||||
Returns active (mine) site for 'project_name' from settings
|
||||
"""
|
||||
return self.get_synced_preset(project_name)['config']['active_site']
|
||||
def _get_configured_sites_from_setting(self, project_setting):
|
||||
if not project_setting.get("enabled"):
|
||||
return {}
|
||||
|
||||
def get_remote_site(self, project_name):
|
||||
initiated_handlers = {}
|
||||
configured_sites = {}
|
||||
all_sites = self._get_default_site_configs()
|
||||
all_sites.update(project_setting.get("sites"))
|
||||
for site_name, config in all_sites.items():
|
||||
handler = initiated_handlers. \
|
||||
get((config["provider"], site_name))
|
||||
if not handler:
|
||||
handler = lib.factory.get_provider(config["provider"],
|
||||
site_name,
|
||||
presets=config)
|
||||
initiated_handlers[(config["provider"], site_name)] = \
|
||||
handler
|
||||
|
||||
if handler.is_active():
|
||||
configured_sites[site_name] = True
|
||||
|
||||
return configured_sites
|
||||
|
||||
def _get_default_site_configs(self):
|
||||
"""
|
||||
Returns remote (theirs) site for 'project_name' from settings
|
||||
Returns skeleton settings for 'studio' and user's local site
|
||||
"""
|
||||
return self.get_synced_preset(project_name)['config']['remote_site']
|
||||
default_config = {'provider': 'local_drive'}
|
||||
all_sites = {self.DEFAULT_SITE: default_config,
|
||||
get_local_site_id(): default_config}
|
||||
return all_sites
|
||||
|
||||
def get_provider_for_site(self, project_name, site):
|
||||
"""
|
||||
Return provider name for site.
|
||||
"""
|
||||
site_preset = self.get_synced_preset(project_name)["sites"].get(site)
|
||||
site_preset = self.get_sync_project_setting(project_name)["sites"].\
|
||||
get(site)
|
||||
if site_preset:
|
||||
return site_preset["provider"]
|
||||
|
||||
|
|
@ -606,8 +740,9 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
]}
|
||||
]
|
||||
}
|
||||
|
||||
log.debug("get_sync_representations.query: {}".format(query))
|
||||
log.debug("active_site:{} - remote_site:{}".format(active_site,
|
||||
remote_site))
|
||||
log.debug("query: {}".format(query))
|
||||
representations = self.connection.find(query)
|
||||
|
||||
return representations
|
||||
|
|
@ -654,7 +789,7 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
return SyncStatus.DO_NOTHING
|
||||
|
||||
async def upload(self, collection, file, representation, provider_name,
|
||||
site_name, tree=None, preset=None):
|
||||
remote_site_name, tree=None, preset=None):
|
||||
"""
|
||||
Upload single 'file' of a 'representation' to 'provider'.
|
||||
Source url is taken from 'file' portion, where {root} placeholder
|
||||
|
|
@ -684,40 +819,40 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
# this part modifies structure on 'remote_site', only single
|
||||
# thread can do that at a time, upload/download to prepared
|
||||
# structure should be run in parallel
|
||||
handler = lib.factory.get_provider(provider_name, site_name,
|
||||
tree=tree, presets=preset)
|
||||
remote_file = self._get_remote_file_path(file,
|
||||
handler.get_roots_config()
|
||||
)
|
||||
remote_handler = lib.factory.get_provider(provider_name,
|
||||
remote_site_name,
|
||||
tree=tree,
|
||||
presets=preset)
|
||||
|
||||
local_file = self.get_local_file_path(collection,
|
||||
file.get("path", ""))
|
||||
file_path = file.get("path", "")
|
||||
local_file_path, remote_file_path = self._resolve_paths(
|
||||
file_path, collection, remote_site_name, remote_handler
|
||||
)
|
||||
|
||||
target_folder = os.path.dirname(remote_file)
|
||||
folder_id = handler.create_folder(target_folder)
|
||||
target_folder = os.path.dirname(remote_file_path)
|
||||
folder_id = remote_handler.create_folder(target_folder)
|
||||
|
||||
if not folder_id:
|
||||
err = "Folder {} wasn't created. Check permissions.".\
|
||||
format(target_folder)
|
||||
raise NotADirectoryError(err)
|
||||
|
||||
remote_site = self.get_remote_site(collection)
|
||||
loop = asyncio.get_running_loop()
|
||||
file_id = await loop.run_in_executor(None,
|
||||
handler.upload_file,
|
||||
local_file,
|
||||
remote_file,
|
||||
remote_handler.upload_file,
|
||||
local_file_path,
|
||||
remote_file_path,
|
||||
self,
|
||||
collection,
|
||||
file,
|
||||
representation,
|
||||
remote_site,
|
||||
remote_site_name,
|
||||
True
|
||||
)
|
||||
return file_id
|
||||
|
||||
async def download(self, collection, file, representation, provider_name,
|
||||
site_name, tree=None, preset=None):
|
||||
remote_site_name, tree=None, preset=None):
|
||||
"""
|
||||
Downloads file to local folder denoted in representation.Context.
|
||||
|
||||
|
|
@ -735,21 +870,24 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
(string) - 'name' of local file
|
||||
"""
|
||||
with self.lock:
|
||||
handler = lib.factory.get_provider(provider_name, site_name,
|
||||
tree=tree, presets=preset)
|
||||
remote_file_path = self._get_remote_file_path(
|
||||
file, handler.get_roots_config())
|
||||
remote_handler = lib.factory.get_provider(provider_name,
|
||||
remote_site_name,
|
||||
tree=tree,
|
||||
presets=preset)
|
||||
|
||||
file_path = file.get("path", "")
|
||||
local_file_path, remote_file_path = self._resolve_paths(
|
||||
file_path, collection, remote_site_name, remote_handler
|
||||
)
|
||||
|
||||
local_file_path = self.get_local_file_path(collection,
|
||||
file.get("path", ""))
|
||||
local_folder = os.path.dirname(local_file_path)
|
||||
os.makedirs(local_folder, exist_ok=True)
|
||||
|
||||
local_site = self.get_local_site(collection)
|
||||
local_site = self.get_active_site(collection)
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
file_id = await loop.run_in_executor(None,
|
||||
handler.download_file,
|
||||
remote_handler.download_file,
|
||||
remote_file_path,
|
||||
local_file_path,
|
||||
self,
|
||||
|
|
@ -907,7 +1045,7 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
raise ValueError("Misconfiguration, only one of side and " +
|
||||
"site_name arguments should be passed.")
|
||||
|
||||
local_site = self.get_local_site(collection)
|
||||
local_site = self.get_active_site(collection)
|
||||
remote_site = self.get_remote_site(collection)
|
||||
|
||||
if side:
|
||||
|
|
@ -1066,19 +1204,14 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
Returns:
|
||||
only logs, catches IndexError and OSError
|
||||
"""
|
||||
my_local_site = self.get_my_local_site(collection)
|
||||
my_local_site = get_local_site_id()
|
||||
if my_local_site != site_name:
|
||||
self.log.warning("Cannot remove non local file for {}".
|
||||
format(site_name))
|
||||
return
|
||||
|
||||
handler = None
|
||||
sites = self.get_active_sites(collection)
|
||||
for provider_name, provider_site_name in sites:
|
||||
if provider_site_name == site_name:
|
||||
handler = lib.factory.get_provider(provider_name,
|
||||
site_name)
|
||||
break
|
||||
provider_name = self.get_provider_for_site(collection, site_name)
|
||||
handler = lib.factory.get_provider(provider_name, site_name)
|
||||
|
||||
if handler and isinstance(handler, LocalDriveHandler):
|
||||
query = {
|
||||
|
|
@ -1093,12 +1226,14 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
return
|
||||
|
||||
representation = representation.pop()
|
||||
local_file_path = ''
|
||||
for file in representation.get("files"):
|
||||
local_file_path, _ = self._resolve_paths(file.get("path", ""),
|
||||
collection
|
||||
)
|
||||
try:
|
||||
self.log.debug("Removing {}".format(file["path"]))
|
||||
local_file = self.get_local_file_path(collection,
|
||||
file.get("path", ""))
|
||||
os.remove(local_file)
|
||||
self.log.debug("Removing {}".format(local_file_path))
|
||||
os.remove(local_file_path)
|
||||
except IndexError:
|
||||
msg = "No file set for {}".format(representation_id)
|
||||
self.log.debug(msg)
|
||||
|
|
@ -1109,31 +1244,13 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
raise ValueError(msg)
|
||||
|
||||
try:
|
||||
folder = os.path.dirname(local_file)
|
||||
folder = os.path.dirname(local_file_path)
|
||||
os.rmdir(folder)
|
||||
except OSError:
|
||||
msg = "folder {} cannot be removed".format(folder)
|
||||
self.log.warning(msg)
|
||||
raise ValueError(msg)
|
||||
|
||||
def get_my_local_site(self, project_name=None):
|
||||
"""
|
||||
Returns name of current user local_site
|
||||
|
||||
Args:
|
||||
project_name (string):
|
||||
Returns:
|
||||
(string)
|
||||
"""
|
||||
if project_name:
|
||||
settings = get_project_settings(project_name)
|
||||
else:
|
||||
settings = get_current_project_settings()
|
||||
|
||||
sync_server_presets = settings["global"]["sync_server"]["config"]
|
||||
|
||||
return sync_server_presets.get("local_id")
|
||||
|
||||
def get_loop_delay(self, project_name):
|
||||
"""
|
||||
Return count of seconds before next synchronization loop starts
|
||||
|
|
@ -1141,7 +1258,8 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
Returns:
|
||||
(int): in seconds
|
||||
"""
|
||||
return int(self.presets[project_name]["config"]["loop_delay"])
|
||||
ld = self.sync_project_settings[project_name]["config"]["loop_delay"]
|
||||
return int(ld)
|
||||
|
||||
def show_widget(self):
|
||||
"""Show dialog to enter credentials"""
|
||||
|
|
@ -1215,59 +1333,35 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
val = {"files.$[f].sites.$[s].progress": progress}
|
||||
return val
|
||||
|
||||
def get_local_file_path(self, collection, path):
|
||||
def _resolve_paths(self, file_path, collection,
|
||||
remote_site_name=None, remote_handler=None):
|
||||
"""
|
||||
Auxiliary function for replacing rootless path with real path
|
||||
Returns tuple of local and remote file paths with {root}
|
||||
placeholders replaced with proper values from Settings or Anatomy
|
||||
|
||||
Works with multi roots.
|
||||
If root definition is not found in Settings, anatomy is used
|
||||
|
||||
Args:
|
||||
collection (string): project name
|
||||
path (dictionary): 'path' to file with {root}
|
||||
|
||||
Returns:
|
||||
(string) - absolute path on local system
|
||||
Args:
|
||||
file_path(string): path with {root}
|
||||
collection(string): project name
|
||||
remote_site_name(string): remote site
|
||||
remote_handler(AbstractProvider): implementation
|
||||
Returns:
|
||||
(string, string) - proper absolute paths
|
||||
"""
|
||||
local_active_site = self.get_local_site(collection)
|
||||
sites = self.get_synced_preset(collection)["sites"]
|
||||
root_config = sites[local_active_site]["root"]
|
||||
remote_file_path = ''
|
||||
if remote_handler:
|
||||
root_configs = self._get_roots_config(self.sync_project_settings,
|
||||
collection,
|
||||
remote_site_name)
|
||||
|
||||
if not root_config.get("root"):
|
||||
root_config = {"root": root_config}
|
||||
remote_file_path = remote_handler.resolve_path(file_path,
|
||||
root_configs)
|
||||
|
||||
try:
|
||||
path = path.format(**root_config)
|
||||
except KeyError:
|
||||
try:
|
||||
anatomy = self.get_anatomy(collection)
|
||||
path = anatomy.fill_root(path)
|
||||
except KeyError:
|
||||
msg = "Error in resolving local root from anatomy"
|
||||
self.log.error(msg)
|
||||
raise ValueError(msg)
|
||||
local_handler = lib.factory.get_provider(
|
||||
'local_drive', self.get_active_site(collection))
|
||||
local_file_path = local_handler.resolve_path(
|
||||
file_path, None, self.get_anatomy(collection))
|
||||
|
||||
return path
|
||||
|
||||
def _get_remote_file_path(self, file, root_config):
|
||||
"""
|
||||
Auxiliary function for replacing rootless path with real path
|
||||
Args:
|
||||
file (dictionary): file info, get 'path' to file with {root}
|
||||
root_config (dict): value of {root} for remote location
|
||||
|
||||
Returns:
|
||||
(string) - absolute path on remote location
|
||||
"""
|
||||
path = file.get("path", "")
|
||||
if not root_config.get("root"):
|
||||
root_config = {"root": root_config}
|
||||
|
||||
try:
|
||||
return path.format(**root_config)
|
||||
except KeyError:
|
||||
msg = "Error in resolving remote root, unknown key"
|
||||
self.log.error(msg)
|
||||
return local_file_path, remote_file_path
|
||||
|
||||
def _get_retries_arr(self, project_name):
|
||||
"""
|
||||
|
|
@ -1278,12 +1372,20 @@ class SyncServer(PypeModule, ITrayModule):
|
|||
Returns:
|
||||
(list)
|
||||
"""
|
||||
retry_cnt = self.presets[project_name].get("config")["retry_cnt"]
|
||||
retry_cnt = self.sync_project_settings[project_name].\
|
||||
get("config")["retry_cnt"]
|
||||
arr = [i for i in range(int(retry_cnt))]
|
||||
arr.append(None)
|
||||
|
||||
return arr
|
||||
|
||||
def _get_roots_config(self, presets, project_name, site_name):
|
||||
"""
|
||||
Returns configured root(s) for 'project_name' and 'site_name' from
|
||||
settings ('presets')
|
||||
"""
|
||||
return presets[project_name]['sites'][site_name]['root']
|
||||
|
||||
|
||||
class SyncServerThread(threading.Thread):
|
||||
"""
|
||||
|
|
@ -1334,20 +1436,20 @@ class SyncServerThread(threading.Thread):
|
|||
while self.is_running and not self.module.is_paused():
|
||||
import time
|
||||
start_time = None
|
||||
for collection, preset in self.module.get_synced_presets().\
|
||||
self.module.set_sync_project_settings() # clean cache
|
||||
for collection, preset in self.module.get_sync_project_settings().\
|
||||
items():
|
||||
if self.module.is_project_paused(collection):
|
||||
start_time = time.time()
|
||||
local_site, remote_site = self._working_sites(collection)
|
||||
if not all([local_site, remote_site]):
|
||||
continue
|
||||
|
||||
start_time = time.time()
|
||||
sync_repres = self.module.get_sync_representations(
|
||||
collection,
|
||||
preset.get('config')["active_site"],
|
||||
preset.get('config')["remote_site"]
|
||||
local_site,
|
||||
remote_site
|
||||
)
|
||||
|
||||
local_site = preset.get('config')["active_site"]
|
||||
remote_site = preset.get('config')["remote_site"]
|
||||
task_files_to_process = []
|
||||
files_processed_info = []
|
||||
# process only unique file paths in one batch
|
||||
|
|
@ -1477,3 +1579,24 @@ class SyncServerThread(threading.Thread):
|
|||
self.executor.shutdown(wait=True)
|
||||
await asyncio.sleep(0.07)
|
||||
self.loop.stop()
|
||||
|
||||
def _working_sites(self, collection):
|
||||
if self.module.is_project_paused(collection):
|
||||
log.debug("Both sites same, skipping")
|
||||
return None, None
|
||||
|
||||
local_site = self.module.get_active_site(collection)
|
||||
remote_site = self.module.get_remote_site(collection)
|
||||
if local_site == remote_site:
|
||||
log.debug("{}-{} sites same, skipping".format(local_site,
|
||||
remote_site))
|
||||
return None, None
|
||||
|
||||
if not all([self.module.site_is_working(collection, local_site),
|
||||
self.module.site_is_working(collection, remote_site)]):
|
||||
log.debug("Some of the sites {} - {} is not ".format(local_site,
|
||||
remote_site) +
|
||||
"working properly")
|
||||
return None, None
|
||||
|
||||
return local_site, remote_site
|
||||
|
|
|
|||
|
|
@ -15,8 +15,7 @@ from avalon.tools.delegates import PrettyTimeDelegate, pretty_timestamp
|
|||
from bson.objectid import ObjectId
|
||||
|
||||
from pype.lib import PypeLogger
|
||||
|
||||
import json
|
||||
from pype.api import get_local_site_id
|
||||
|
||||
log = PypeLogger().get_logger("SyncServer")
|
||||
|
||||
|
|
@ -29,6 +28,8 @@ STATUS = {
|
|||
-1: 'Not available'
|
||||
}
|
||||
|
||||
DUMMY_PROJECT = "No project configured"
|
||||
|
||||
|
||||
class SyncServerWindow(QtWidgets.QDialog):
|
||||
"""
|
||||
|
|
@ -157,7 +158,9 @@ class SyncProjectListWidget(ProjectListWidget):
|
|||
model = self.project_list.model()
|
||||
model.clear()
|
||||
|
||||
for project_name in self.sync_server.get_synced_presets().keys():
|
||||
project_name = None
|
||||
for project_name in self.sync_server.get_sync_project_settings().\
|
||||
keys():
|
||||
if self.sync_server.is_paused() or \
|
||||
self.sync_server.is_project_paused(project_name):
|
||||
icon = self._get_icon("paused")
|
||||
|
|
@ -166,8 +169,8 @@ class SyncProjectListWidget(ProjectListWidget):
|
|||
|
||||
model.appendRow(QtGui.QStandardItem(icon, project_name))
|
||||
|
||||
if len(self.sync_server.get_synced_presets().keys()) == 0:
|
||||
model.appendRow(QtGui.QStandardItem("No project configured"))
|
||||
if len(self.sync_server.get_sync_project_settings().keys()) == 0:
|
||||
model.appendRow(QtGui.QStandardItem(DUMMY_PROJECT))
|
||||
|
||||
self.current_project = self.project_list.currentIndex().data(
|
||||
QtCore.Qt.DisplayRole
|
||||
|
|
@ -176,7 +179,8 @@ class SyncProjectListWidget(ProjectListWidget):
|
|||
self.current_project = self.project_list.model().item(0). \
|
||||
data(QtCore.Qt.DisplayRole)
|
||||
|
||||
self.local_site = self.sync_server.get_local_site(project_name)
|
||||
if project_name:
|
||||
self.local_site = self.sync_server.get_active_site(project_name)
|
||||
|
||||
def _get_icon(self, status):
|
||||
if not self.icons.get(status):
|
||||
|
|
@ -200,7 +204,6 @@ class SyncProjectListWidget(ProjectListWidget):
|
|||
menu = QtWidgets.QMenu()
|
||||
actions_mapping = {}
|
||||
|
||||
action = None
|
||||
if self.sync_server.is_project_paused(self.project_name):
|
||||
action = QtWidgets.QAction("Unpause")
|
||||
actions_mapping[action] = self._unpause
|
||||
|
|
@ -209,8 +212,7 @@ class SyncProjectListWidget(ProjectListWidget):
|
|||
actions_mapping[action] = self._pause
|
||||
menu.addAction(action)
|
||||
|
||||
if self.local_site == self.sync_server.get_my_local_site(
|
||||
self.project_name):
|
||||
if self.local_site == get_local_site_id():
|
||||
action = QtWidgets.QAction("Clear local project")
|
||||
actions_mapping[action] = self._clear_project
|
||||
menu.addAction(action)
|
||||
|
|
@ -239,6 +241,7 @@ class SyncProjectListWidget(ProjectListWidget):
|
|||
self.project_name = None
|
||||
self.refresh()
|
||||
|
||||
|
||||
class ProjectModel(QtCore.QAbstractListModel):
|
||||
def __init__(self, *args, projects=None, **kwargs):
|
||||
super(ProjectModel, self).__init__(*args, **kwargs)
|
||||
|
|
@ -254,6 +257,7 @@ class ProjectModel(QtCore.QAbstractListModel):
|
|||
def rowCount(self, index):
|
||||
return len(self.todos)
|
||||
|
||||
|
||||
class SyncRepresentationWidget(QtWidgets.QWidget):
|
||||
"""
|
||||
Summary dialog with list of representations that matches current
|
||||
|
|
@ -473,10 +477,10 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
def _add_site(self):
|
||||
log.info(self.representation_id)
|
||||
project_name = self.table_view.model()._project
|
||||
local_site_name = self.sync_server.get_my_local_site(project_name)
|
||||
local_site_name = self.sync_server.get_my_local_site()
|
||||
try:
|
||||
self.sync_server.add_site(
|
||||
self.table_view.model()._project,
|
||||
project_name,
|
||||
self.representation_id,
|
||||
local_site_name
|
||||
)
|
||||
|
|
@ -498,13 +502,14 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
"""
|
||||
log.info("Removing {}".format(self.representation_id))
|
||||
try:
|
||||
local_site = get_local_site_id()
|
||||
self.sync_server.remove_site(
|
||||
self.table_view.model()._project,
|
||||
self.representation_id,
|
||||
'local_0',
|
||||
local_site,
|
||||
True
|
||||
)
|
||||
self.message_generated.emit("Site local_0 removed")
|
||||
self.message_generated.emit("Site {} removed".format(local_site))
|
||||
except ValueError as exp:
|
||||
self.message_generated.emit("Error {}".format(str(exp)))
|
||||
|
||||
|
|
@ -535,6 +540,9 @@ class SyncRepresentationWidget(QtWidgets.QWidget):
|
|||
return
|
||||
|
||||
fpath = self.item.path
|
||||
project = self.table_view.model()._project
|
||||
fpath = self.sync_server.get_local_file_path(project, fpath)
|
||||
|
||||
fpath = os.path.normpath(os.path.dirname(fpath))
|
||||
if os.path.isdir(fpath):
|
||||
if 'win' in sys.platform: # windows
|
||||
|
|
@ -620,11 +628,13 @@ class SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
self.filter = None
|
||||
|
||||
self._initialized = False
|
||||
if not self._project or self._project == DUMMY_PROJECT:
|
||||
return
|
||||
|
||||
self.sync_server = sync_server
|
||||
# TODO think about admin mode
|
||||
# this is for regular user, always only single local and single remote
|
||||
self.local_site = self.sync_server.get_local_site(self._project)
|
||||
self.local_site = self.sync_server.get_active_site(self._project)
|
||||
self.remote_site = self.sync_server.get_remote_site(self._project)
|
||||
|
||||
self.projection = self.get_default_projection()
|
||||
|
|
@ -790,14 +800,12 @@ class SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
repre.get("files_size", 0),
|
||||
1,
|
||||
STATUS[repre.get("status", -1)],
|
||||
self.sync_server.get_local_file_path(self._project,
|
||||
files[0].get('path'))
|
||||
files[0].get('path')
|
||||
)
|
||||
|
||||
self._data.append(item)
|
||||
self._rec_loaded += 1
|
||||
|
||||
|
||||
def canFetchMore(self, index):
|
||||
"""
|
||||
Check if there are more records than currently loaded
|
||||
|
|
@ -849,6 +857,9 @@ class SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
|
||||
self.sort = {self.SORT_BY_COLUMN[index]: order, '_id': 1}
|
||||
self.query = self.get_default_query()
|
||||
# import json
|
||||
# log.debug(json.dumps(self.query, indent=4).replace('False', 'false').\
|
||||
# replace('True', 'true').replace('None', 'null'))
|
||||
|
||||
representations = self.dbcon.aggregate(self.query)
|
||||
self.refresh(representations)
|
||||
|
|
@ -871,7 +882,8 @@ class SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
project (str): name of project
|
||||
"""
|
||||
self._project = project
|
||||
self.local_site = self.sync_server.get_local_site(self._project)
|
||||
self.sync_server.set_sync_project_settings()
|
||||
self.local_site = self.sync_server.get_active_site(self._project)
|
||||
self.remote_site = self.sync_server.get_remote_site(self._project)
|
||||
self.refresh()
|
||||
|
||||
|
|
@ -886,7 +898,6 @@ class SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
Returns:
|
||||
(QModelIndex)
|
||||
"""
|
||||
index = None
|
||||
for i in range(self.rowCount(None)):
|
||||
index = self.index(i, 0)
|
||||
value = self.data(index, Qt.UserRole)
|
||||
|
|
@ -995,7 +1006,7 @@ class SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
0]},
|
||||
'failed_remote_tries': {
|
||||
'$cond': [{'$size': '$order_remote.tries'},
|
||||
{'$first': '$order_local.tries'},
|
||||
{'$first': '$order_remote.tries'},
|
||||
0]},
|
||||
'paused_remote': {
|
||||
'$cond': [{'$size': "$order_remote.paused"},
|
||||
|
|
@ -1022,9 +1033,9 @@ class SyncRepresentationModel(QtCore.QAbstractTableModel):
|
|||
# select last touch of file
|
||||
'updated_dt_remote': {'$max': "$updated_dt_remote"},
|
||||
'failed_remote': {'$sum': '$failed_remote'},
|
||||
'failed_local': {'$sum': '$paused_remote'},
|
||||
'failed_local_tries': {'$sum': '$failed_local_tries'},
|
||||
'failed_local': {'$sum': '$failed_local'},
|
||||
'failed_remote_tries': {'$sum': '$failed_remote_tries'},
|
||||
'failed_local_tries': {'$sum': '$failed_local_tries'},
|
||||
'paused_remote': {'$sum': '$paused_remote'},
|
||||
'paused_local': {'$sum': '$paused_local'},
|
||||
'updated_dt_local': {'$max': "$updated_dt_local"}
|
||||
|
|
@ -1381,8 +1392,10 @@ class SyncRepresentationDetailWidget(QtWidgets.QWidget):
|
|||
return
|
||||
|
||||
fpath = self.item.path
|
||||
fpath = os.path.normpath(os.path.dirname(fpath))
|
||||
project = self.table_view.model()._project
|
||||
fpath = self.sync_server.get_local_file_path(project, fpath)
|
||||
|
||||
fpath = os.path.normpath(os.path.dirname(fpath))
|
||||
if os.path.isdir(fpath):
|
||||
if 'win' in sys.platform: # windows
|
||||
subprocess.Popen('explorer "%s"' % fpath)
|
||||
|
|
@ -1460,7 +1473,7 @@ class SyncRepresentationDetailModel(QtCore.QAbstractTableModel):
|
|||
self.sync_server = sync_server
|
||||
# TODO think about admin mode
|
||||
# this is for regular user, always only single local and single remote
|
||||
self.local_site = self.sync_server.get_local_site(self._project)
|
||||
self.local_site = self.sync_server.get_active_site(self._project)
|
||||
self.remote_site = self.sync_server.get_remote_site(self._project)
|
||||
|
||||
self.sort = self.DEFAULT_SORT
|
||||
|
|
@ -1595,8 +1608,7 @@ class SyncRepresentationDetailModel(QtCore.QAbstractTableModel):
|
|||
STATUS[repre.get("status", -1)],
|
||||
repre.get("tries"),
|
||||
'\n'.join(errors),
|
||||
self.sync_server.get_local_file_path(self._project,
|
||||
file.get('path'))
|
||||
file.get('path')
|
||||
|
||||
)
|
||||
self._data.append(item)
|
||||
|
|
@ -1664,7 +1676,6 @@ class SyncRepresentationDetailModel(QtCore.QAbstractTableModel):
|
|||
Returns:
|
||||
(QModelIndex)
|
||||
"""
|
||||
index = None
|
||||
for i in range(self.rowCount(None)):
|
||||
index = self.index(i, 0)
|
||||
value = self.data(index, Qt.UserRole)
|
||||
|
|
@ -1772,14 +1783,15 @@ class SyncRepresentationDetailModel(QtCore.QAbstractTableModel):
|
|||
"$order_local.error",
|
||||
[""]]}},
|
||||
'tries': {'$first': {
|
||||
'$cond': [{'$size': "$order_local.tries"},
|
||||
"$order_local.tries",
|
||||
{'$cond': [
|
||||
{'$size': "$order_remote.tries"},
|
||||
"$order_remote.tries",
|
||||
[]
|
||||
]}
|
||||
]}}
|
||||
'$cond': [
|
||||
{'$size': "$order_local.tries"},
|
||||
"$order_local.tries",
|
||||
{'$cond': [
|
||||
{'$size': "$order_remote.tries"},
|
||||
"$order_remote.tries",
|
||||
[]
|
||||
]}
|
||||
]}}
|
||||
}},
|
||||
{"$project": self.projection},
|
||||
{"$sort": self.sort},
|
||||
|
|
@ -2010,6 +2022,7 @@ class SizeDelegate(QtWidgets.QStyledItemDelegate):
|
|||
value /= 1024.0
|
||||
return "%.1f%s%s" % (value, 'Yi', suffix)
|
||||
|
||||
|
||||
def _convert_progress(value):
|
||||
try:
|
||||
progress = float(value)
|
||||
|
|
|
|||
|
|
@ -966,10 +966,16 @@ class IntegrateAssetNew(pyblish.api.InstancePlugin):
|
|||
["global"]
|
||||
["sync_server"])
|
||||
|
||||
local_site_id = pype.api.get_local_site_id()
|
||||
if sync_server_presets["enabled"]:
|
||||
local_site = sync_server_presets["config"].\
|
||||
get("active_site", "studio").strip()
|
||||
if local_site == 'local':
|
||||
local_site = local_site_id
|
||||
|
||||
remote_site = sync_server_presets["config"].get("remote_site")
|
||||
if remote_site == 'local':
|
||||
remote_site = local_site_id
|
||||
|
||||
rec = {
|
||||
"_id": io.ObjectId(),
|
||||
|
|
|
|||
|
|
@ -198,11 +198,10 @@
|
|||
"sync_server": {
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"local_id": "local_0",
|
||||
"retry_cnt": "3",
|
||||
"loop_delay": "60",
|
||||
"active_site": "studio",
|
||||
"remote_site": "gdrive"
|
||||
"remote_site": "studio"
|
||||
},
|
||||
"sites": {
|
||||
"gdrive": {
|
||||
|
|
@ -211,20 +210,6 @@
|
|||
"root": {
|
||||
"work": ""
|
||||
}
|
||||
},
|
||||
"studio": {
|
||||
"provider": "local_drive",
|
||||
"credentials_url": "",
|
||||
"root": {
|
||||
"work": ""
|
||||
}
|
||||
},
|
||||
"local_0": {
|
||||
"provider": "local_drive",
|
||||
"credentials_url": "",
|
||||
"root": {
|
||||
"work": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,12 +17,6 @@
|
|||
"label": "Config",
|
||||
"collapsible": true,
|
||||
"children": [
|
||||
|
||||
{
|
||||
"type": "text",
|
||||
"key": "local_id",
|
||||
"label": "Local ID"
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"key": "retry_cnt",
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ def apply_local_settings_on_system_settings(system_settings, local_settings):
|
|||
|
||||
|
||||
def apply_local_settings_on_anatomy_settings(
|
||||
anatomy_settings, local_settings, project_name
|
||||
anatomy_settings, local_settings, project_name, site_name=None
|
||||
):
|
||||
"""Apply local settings on anatomy settings.
|
||||
|
||||
|
|
@ -398,36 +398,40 @@ def apply_local_settings_on_anatomy_settings(
|
|||
if not local_settings:
|
||||
return
|
||||
|
||||
local_project_settings = local_settings.get("projects")
|
||||
if not local_project_settings:
|
||||
local_project_settings = local_settings.get("projects") or {}
|
||||
|
||||
# Check for roots existence in local settings first
|
||||
roots_project_locals = (
|
||||
local_project_settings
|
||||
.get(project_name, {})
|
||||
.get("roots", {})
|
||||
)
|
||||
roots_default_locals = (
|
||||
local_project_settings
|
||||
.get(DEFAULT_PROJECT_KEY, {})
|
||||
.get("roots", {})
|
||||
)
|
||||
|
||||
# Skip rest of processing if roots are not set
|
||||
if not roots_project_locals and not roots_default_locals:
|
||||
return
|
||||
|
||||
project_locals = local_project_settings.get(project_name) or {}
|
||||
default_locals = local_project_settings.get(DEFAULT_PROJECT_KEY) or {}
|
||||
active_site = project_locals.get("active_site")
|
||||
if not active_site:
|
||||
active_site = default_locals.get("active_site")
|
||||
|
||||
if not active_site:
|
||||
# Get active site from settings
|
||||
if site_name is None:
|
||||
project_settings = get_project_settings(project_name)
|
||||
active_site = (
|
||||
project_settings
|
||||
["global"]
|
||||
["sync_server"]
|
||||
["config"]
|
||||
["active_site"]
|
||||
site_name = (
|
||||
project_settings["global"]["sync_server"]["config"]["active_site"]
|
||||
)
|
||||
|
||||
# QUESTION should raise an exception?
|
||||
if not active_site:
|
||||
if not site_name:
|
||||
return
|
||||
|
||||
roots_locals = default_locals.get("roots", {}).get(active_site, {})
|
||||
if project_name != DEFAULT_PROJECT_KEY:
|
||||
roots_locals.update(
|
||||
project_locals.get("roots", {}).get(active_site, {})
|
||||
)
|
||||
|
||||
# Combine roots from local settings
|
||||
roots_locals = roots_default_locals.get(site_name) or {}
|
||||
roots_locals.update(roots_project_locals.get(site_name) or {})
|
||||
# Skip processing if roots for current active site are not available in
|
||||
# local settings
|
||||
if not roots_locals:
|
||||
return
|
||||
|
||||
|
|
@ -442,6 +446,44 @@ def apply_local_settings_on_anatomy_settings(
|
|||
)
|
||||
|
||||
|
||||
def apply_local_settings_on_project_settings(
|
||||
project_settings, local_settings, project_name
|
||||
):
|
||||
"""Apply local settings on project settings.
|
||||
|
||||
Currently is modifying active site and remote site in sync server.
|
||||
|
||||
Args:
|
||||
project_settings (dict): Data for project settings.
|
||||
local_settings (dict): Data of local settings.
|
||||
project_name (str): Name of project for which settings data are.
|
||||
"""
|
||||
if not local_settings:
|
||||
return
|
||||
|
||||
local_project_settings = local_settings.get("projects")
|
||||
if not local_project_settings:
|
||||
return
|
||||
|
||||
project_locals = local_project_settings.get(project_name) or {}
|
||||
default_locals = local_project_settings.get(DEFAULT_PROJECT_KEY) or {}
|
||||
active_site = (
|
||||
project_locals.get("active_site")
|
||||
or default_locals.get("active_site")
|
||||
)
|
||||
remote_site = (
|
||||
project_locals.get("remote_site")
|
||||
or default_locals.get("remote_site")
|
||||
)
|
||||
|
||||
sync_server_config = project_settings["global"]["sync_server"]["config"]
|
||||
if active_site:
|
||||
sync_server_config["active_site"] = active_site
|
||||
|
||||
if remote_site:
|
||||
sync_server_config["remote_site"] = remote_site
|
||||
|
||||
|
||||
def get_system_settings(clear_metadata=True):
|
||||
"""System settings with applied studio overrides."""
|
||||
default_values = get_default_settings()[SYSTEM_SETTINGS_KEY]
|
||||
|
|
@ -463,6 +505,8 @@ def get_default_project_settings(clear_metadata=True):
|
|||
result = apply_overrides(default_values, studio_values)
|
||||
if clear_metadata:
|
||||
clear_metadata_from_settings(result)
|
||||
local_settings = get_local_settings()
|
||||
apply_local_settings_on_project_settings(result, local_settings, None)
|
||||
return result
|
||||
|
||||
|
||||
|
|
@ -485,7 +529,7 @@ def get_default_anatomy_settings(clear_metadata=True):
|
|||
return result
|
||||
|
||||
|
||||
def get_anatomy_settings(project_name, clear_metadata=True):
|
||||
def get_anatomy_settings(project_name, site_name=None, exclude_locals=False):
|
||||
"""Project anatomy data with applied studio and project overrides."""
|
||||
if not project_name:
|
||||
raise ValueError(
|
||||
|
|
@ -498,23 +542,19 @@ def get_anatomy_settings(project_name, clear_metadata=True):
|
|||
project_name
|
||||
)
|
||||
|
||||
# TODO uncomment and remove hotfix result when overrides of anatomy
|
||||
# are stored correctly.
|
||||
# result = apply_overrides(studio_overrides, project_overrides)
|
||||
result = copy.deepcopy(studio_overrides)
|
||||
if project_overrides:
|
||||
for key, value in project_overrides.items():
|
||||
result[key] = value
|
||||
if clear_metadata:
|
||||
clear_metadata_from_settings(result)
|
||||
result = apply_overrides(studio_overrides, project_overrides)
|
||||
|
||||
clear_metadata_from_settings(result)
|
||||
|
||||
if not exclude_locals:
|
||||
local_settings = get_local_settings()
|
||||
apply_local_settings_on_anatomy_settings(
|
||||
result, local_settings, project_name
|
||||
result, local_settings, project_name, site_name
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def get_project_settings(project_name, clear_metadata=True):
|
||||
def get_project_settings(project_name, exclude_locals=False):
|
||||
"""Project settings with applied studio and project overrides."""
|
||||
if not project_name:
|
||||
raise ValueError(
|
||||
|
|
@ -528,8 +568,14 @@ def get_project_settings(project_name, clear_metadata=True):
|
|||
)
|
||||
|
||||
result = apply_overrides(studio_overrides, project_overrides)
|
||||
if clear_metadata:
|
||||
clear_metadata_from_settings(result)
|
||||
clear_metadata_from_settings(result)
|
||||
|
||||
if not exclude_locals:
|
||||
local_settings = get_local_settings()
|
||||
apply_local_settings_on_project_settings(
|
||||
result, local_settings, project_name
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,6 @@ from .constants import (
|
|||
NOT_SET = type("NOT_SET", (), {})()
|
||||
|
||||
|
||||
def get_active_sites(project_settings):
|
||||
global_entity = project_settings["project_settings"]["global"]
|
||||
sites_entity = global_entity["sync_server"]["sites"]
|
||||
return tuple(sites_entity.keys())
|
||||
|
||||
|
||||
class _ProjectListWidget(ProjectListWidget):
|
||||
def on_item_clicked(self, new_index):
|
||||
new_project_name = new_index.data(QtCore.Qt.DisplayRole)
|
||||
|
|
@ -223,9 +217,10 @@ class RootInputWidget(QtWidgets.QWidget):
|
|||
|
||||
|
||||
class RootsWidget(QtWidgets.QWidget):
|
||||
def __init__(self, project_settings, parent):
|
||||
def __init__(self, modules_manager, project_settings, parent):
|
||||
super(RootsWidget, self).__init__(parent)
|
||||
|
||||
self.modules_manager = modules_manager
|
||||
self.project_settings = project_settings
|
||||
self.site_widgets = []
|
||||
self.local_project_settings = None
|
||||
|
|
@ -241,6 +236,15 @@ class RootsWidget(QtWidgets.QWidget):
|
|||
self.content_layout.removeItem(item)
|
||||
self.site_widgets = []
|
||||
|
||||
def _get_active_sites(self):
|
||||
sync_server_module = (
|
||||
self.modules_manager.modules_by_name["sync_server"]
|
||||
)
|
||||
|
||||
return sync_server_module.get_active_sites_from_settings(
|
||||
self.project_settings["project_settings"].value
|
||||
)
|
||||
|
||||
def refresh(self):
|
||||
self._clear_widgets()
|
||||
|
||||
|
|
@ -251,7 +255,7 @@ class RootsWidget(QtWidgets.QWidget):
|
|||
self.project_settings[PROJECT_ANATOMY_KEY][LOCAL_ROOTS_KEY]
|
||||
)
|
||||
# Site label
|
||||
for site_name in get_active_sites(self.project_settings):
|
||||
for site_name in self._get_active_sites():
|
||||
site_widget = QtWidgets.QWidget(self)
|
||||
site_layout = QtWidgets.QVBoxLayout(site_widget)
|
||||
|
||||
|
|
@ -294,10 +298,12 @@ class RootsWidget(QtWidgets.QWidget):
|
|||
class _SiteCombobox(QtWidgets.QWidget):
|
||||
input_label = None
|
||||
|
||||
def __init__(self, project_settings, parent):
|
||||
def __init__(self, modules_manager, project_settings, parent):
|
||||
super(_SiteCombobox, self).__init__(parent)
|
||||
self.project_settings = project_settings
|
||||
|
||||
self.modules_manager = modules_manager
|
||||
|
||||
self.local_project_settings = None
|
||||
self.local_project_settings_orig = None
|
||||
self.project_name = None
|
||||
|
|
@ -547,7 +553,14 @@ class AciveSiteCombo(_SiteCombobox):
|
|||
input_label = "Active site"
|
||||
|
||||
def _get_project_sites(self):
|
||||
return get_active_sites(self.project_settings)
|
||||
sync_server_module = (
|
||||
self.modules_manager.modules_by_name["sync_server"]
|
||||
)
|
||||
if self.project_name is None:
|
||||
return sync_server_module.get_active_sites_from_settings(
|
||||
self.project_settings["project_settings"].value
|
||||
)
|
||||
return sync_server_module.get_active_sites(self.project_name)
|
||||
|
||||
def _get_local_settings_item(self, project_name=None, data=None):
|
||||
if project_name is None:
|
||||
|
|
@ -575,9 +588,14 @@ class RemoteSiteCombo(_SiteCombobox):
|
|||
input_label = "Remote site"
|
||||
|
||||
def _get_project_sites(self):
|
||||
global_entity = self.project_settings["project_settings"]["global"]
|
||||
sites_entity = global_entity["sync_server"]["sites"]
|
||||
return tuple(sites_entity.keys())
|
||||
sync_server_module = (
|
||||
self.modules_manager.modules_by_name["sync_server"]
|
||||
)
|
||||
if self.project_name is None:
|
||||
return sync_server_module.get_remote_sites_from_settings(
|
||||
self.project_settings["project_settings"].value
|
||||
)
|
||||
return sync_server_module.get_remote_sites(self.project_name)
|
||||
|
||||
def _get_local_settings_item(self, project_name=None, data=None):
|
||||
if project_name is None:
|
||||
|
|
@ -601,17 +619,22 @@ class RemoteSiteCombo(_SiteCombobox):
|
|||
|
||||
|
||||
class RootSiteWidget(QtWidgets.QWidget):
|
||||
def __init__(self, project_settings, parent):
|
||||
def __init__(self, modules_manager, project_settings, parent):
|
||||
self._parent_widget = parent
|
||||
super(RootSiteWidget, self).__init__(parent)
|
||||
|
||||
self.modules_manager = modules_manager
|
||||
self.project_settings = project_settings
|
||||
self._project_name = None
|
||||
|
||||
sites_widget = QtWidgets.QWidget(self)
|
||||
|
||||
active_site_widget = AciveSiteCombo(project_settings, sites_widget)
|
||||
remote_site_widget = RemoteSiteCombo(project_settings, sites_widget)
|
||||
active_site_widget = AciveSiteCombo(
|
||||
modules_manager, project_settings, sites_widget
|
||||
)
|
||||
remote_site_widget = RemoteSiteCombo(
|
||||
modules_manager, project_settings, sites_widget
|
||||
)
|
||||
|
||||
sites_layout = QtWidgets.QHBoxLayout(sites_widget)
|
||||
sites_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -619,7 +642,7 @@ class RootSiteWidget(QtWidgets.QWidget):
|
|||
sites_layout.addWidget(remote_site_widget)
|
||||
sites_layout.addWidget(SpacerWidget(self), 1)
|
||||
|
||||
roots_widget = RootsWidget(project_settings, self)
|
||||
roots_widget = RootsWidget(modules_manager, project_settings, self)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.addWidget(sites_widget)
|
||||
|
|
@ -669,13 +692,17 @@ class ProjectValue(dict):
|
|||
|
||||
|
||||
class ProjectSettingsWidget(QtWidgets.QWidget):
|
||||
def __init__(self, project_settings, parent):
|
||||
def __init__(self, modules_manager, project_settings, parent):
|
||||
super(ProjectSettingsWidget, self).__init__(parent)
|
||||
|
||||
self.local_project_settings = {}
|
||||
|
||||
self.modules_manager = modules_manager
|
||||
|
||||
projects_widget = _ProjectListWidget(self)
|
||||
roos_site_widget = RootSiteWidget(project_settings, self)
|
||||
roos_site_widget = RootSiteWidget(
|
||||
modules_manager, project_settings, self
|
||||
)
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ from pype.api import (
|
|||
SystemSettings,
|
||||
ProjectSettings
|
||||
)
|
||||
from pype.modules import ModulesManager
|
||||
|
||||
from .widgets import (
|
||||
SpacerWidget,
|
||||
|
|
@ -37,6 +38,7 @@ class LocalSettingsWidget(QtWidgets.QWidget):
|
|||
|
||||
self.system_settings = SystemSettings()
|
||||
self.project_settings = ProjectSettings()
|
||||
self.modules_manager = ModulesManager()
|
||||
|
||||
self.main_layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
|
|
@ -108,7 +110,9 @@ class LocalSettingsWidget(QtWidgets.QWidget):
|
|||
project_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0)
|
||||
project_expand_widget.set_content_widget(project_content)
|
||||
|
||||
projects_widget = ProjectSettingsWidget(self.project_settings, self)
|
||||
projects_widget = ProjectSettingsWidget(
|
||||
self.modules_manager, self.project_settings, self
|
||||
)
|
||||
project_layout.addWidget(projects_widget)
|
||||
|
||||
self.main_layout.addWidget(project_expand_widget)
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 7adabe8f0e6858bfe5b6bf0b39bd428ed72d0452
|
||||
Loading…
Add table
Add a link
Reference in a new issue