Source code for tank.util.local_file_storage

# Copyright (c) 2016 Shotgun Software Inc.
#
# CONFIDENTIAL AND PROPRIETARY
#
# This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit
# Source Code License included in this distribution package. See LICENSE.
# By accessing, using, copying or modifying this work you indicate your
# agreement to the Shotgun Pipeline Toolkit Source Code License. All rights
# not expressly granted therein are reserved by Shotgun Software Inc.

import os
from tank_vendor.six.moves import urllib
from . import filesystem
from .platforms import is_linux, is_macos, is_windows
from .. import LogManager
from ..errors import TankError
from tank_vendor.shotgun_api3.lib import sgsix

log = LogManager.get_logger(__name__)


[docs]class LocalFileStorageManager(object): """ Class that encapsulates logic for resolving local storage paths. Toolkit needs to store cache data, logs and other items at runtime. Some of this data is global, other is per site or per configuration. This class provides a consistent and centralized interface for resolving such paths and also handles compatibility across generations of path standards if and when these change between releases. .. note:: All paths returned by this class are local to the currently running user and typically private or with limited access settings for other users. If the current user's home directory is not an appropriate location to store your user files, you can use the ``SHOTGUN_HOME`` environment variable to override the root location of the files. In that case, the location for the user files on each platform will be: - Logging: ``$SHOTGUN_HOME/logs`` - Cache: ``$SHOTGUN_HOME`` - Persistent: ``$SHOTGUN_HOME/data`` - Preferences: ``$SHOTGUN_HOME/preferences`` :constant CORE_V17: Indicates compatibility with Core 0.17 or earlier :constant CORE_V18: Indicates compatibility with Core 0.18 or later :constant LOGGING: Indicates a path suitable for storing logs, useful for debugging :constant CACHE: Indicates a path suitable for storing cache data that can be deleted without any loss of functionality or state. :constant PERSISTENT: Indicates a path suitable for storing data that needs to be retained between sessions. :constant PREFERENCES: Indicates a path that suitable for storing settings files and preferences. """ # generation of path structures (CORE_V17, CORE_V18) = range(2) # supported types of paths (LOGGING, CACHE, PERSISTENT, PREFERENCES) = range(4)
[docs] @classmethod def get_global_root(cls, path_type, generation=CORE_V18): """ Returns a generic Shotgun storage root. The following paths will be used: - On the mac, paths will point into ``~/Library/PATH_TYPE/Shotgun``, where PATH_TYPE is controlled by the path_type property. - On Windows, paths will created below a ``%APPDATA%/Shotgun`` root point. - On Linux, paths will be created below a ``~/.shotgun`` root point. .. note:: This method does not ensure that the folder exists. :param path_type: Type of path to return. One of ``LocalFileStorageManager.LOGGING``, ``LocalFileStorageManager.CACHE``, ``LocalFileStorageManager.PERSISTENT``, where logging is a path where log- and debug related data should be stored, cache is a location intended for cache data, e.g. data that can be deleted without affecting the state of execution, and persistent is a location intended for data that is meant to be persist. This includes things like settings and preferences. :param generation: Path standard generation to use. Defaults to ``LocalFileStorageManager.CORE_V18``, which is the current generation of paths. :return: Path as string """ # If SHOTGUN_HOME is set, the intent is to not use any of official locations and instead use # a sandbox. # # If we still allowed the LocalFileStorageManager to return paths outside of SHOTGUN_HOME, # it would mean that data from outside SHOTGUN_HOME could leak into it and that a user # couldn't be confident that the sandbox was self-contained. # If the environment variable is available and set to an actual value. shotgun_home_override = os.environ.get("SHOTGUN_HOME") if generation == cls.CORE_V18 or shotgun_home_override: if shotgun_home_override: # Make sure environment variables and ~ are evaluated. shotgun_home_override = os.path.expanduser( os.path.expandvars(shotgun_home_override) ) # Make sure the path is an absolute path. shotgun_home_override = os.path.abspath(shotgun_home_override) # Root everything inside that custom path. if path_type == cls.CACHE: return shotgun_home_override elif path_type == cls.PERSISTENT: return os.path.join(shotgun_home_override, "data") elif path_type == cls.PREFERENCES: return os.path.join(shotgun_home_override, "preferences") elif path_type == cls.LOGGING: return os.path.join(shotgun_home_override, "logs") else: raise ValueError("Unsupported path type!") # current generation of paths elif is_macos(): if path_type == cls.CACHE: return os.path.expanduser("~/Library/Caches/Shotgun") elif path_type == cls.PERSISTENT: return os.path.expanduser("~/Library/Application Support/Shotgun") elif path_type == cls.PREFERENCES: return os.path.expanduser("~/Library/Preferences/Shotgun") elif path_type == cls.LOGGING: return os.path.expanduser("~/Library/Logs/Shotgun") else: raise ValueError("Unsupported path type!") elif is_windows(): app_data = os.environ.get("APPDATA", "APPDATA_NOT_SET") if path_type == cls.CACHE: return os.path.join(app_data, "Shotgun") elif path_type == cls.PERSISTENT: return os.path.join(app_data, "Shotgun", "Data") elif path_type == cls.PREFERENCES: return os.path.join(app_data, "Shotgun", "Preferences") elif path_type == cls.LOGGING: return os.path.join(app_data, "Shotgun", "Logs") else: raise ValueError("Unsupported path type!") elif is_linux(): if path_type == cls.CACHE: return os.path.expanduser("~/.shotgun") elif path_type == cls.PERSISTENT: return os.path.expanduser("~/.shotgun/data") elif path_type == cls.PREFERENCES: return os.path.expanduser("~/.shotgun/preferences") elif path_type == cls.LOGGING: return os.path.expanduser("~/.shotgun/logs") else: raise ValueError("Unsupported path type!") else: raise ValueError("Unknown platform: %s" % sgsix.platform) if generation == cls.CORE_V17: # previous generation of paths if is_macos(): if path_type == cls.CACHE: return os.path.expanduser("~/Library/Caches/Shotgun") elif path_type == cls.PERSISTENT: return os.path.expanduser("~/Library/Application Support/Shotgun") elif path_type == cls.LOGGING: return os.path.expanduser("~/Library/Logs/Shotgun") else: raise ValueError("Unsupported path type!") elif is_windows(): if path_type == cls.CACHE: return os.path.join( os.environ.get("APPDATA", "APPDATA_NOT_SET"), "Shotgun" ) elif path_type == cls.PERSISTENT: return os.path.join( os.environ.get("APPDATA", "APPDATA_NOT_SET"), "Shotgun" ) elif path_type == cls.LOGGING: return os.path.join( os.environ.get("APPDATA", "APPDATA_NOT_SET"), "Shotgun" ) else: raise ValueError("Unsupported path type!") elif is_linux(): if path_type == cls.CACHE: return os.path.expanduser("~/.shotgun") elif path_type == cls.PERSISTENT: return os.path.expanduser("~/.shotgun") elif path_type == cls.LOGGING: return os.path.expanduser("~/.shotgun") else: raise ValueError("Unsupported path type!") else: raise ValueError("Unknown platform: %s" % sgsix.platform)
[docs] @classmethod def get_site_root(cls, hostname, path_type, generation=CORE_V18): """ Returns a cache root where items can be stored on a per site basis. For more details, see :meth:`LocalFileStorageManager.get_global_root`. .. note:: This method does not ensure that the folder exists. :param hostname: Shotgun hostname as string, e.g. 'https://foo.shotgunstudio.com' :param path_type: Type of path to return. One of ``LocalFileStorageManager.LOGGING``, ``LocalFileStorageManager.CACHE``, ``LocalFileStorageManager.PERSISTENT``, where logging is a path where log- and debug related data should be stored, cache is a location intended for cache data, e.g. data that can be deleted without affecting the state of execution, and persistent is a location intended for data that is meant to be persist. This includes things like settings and preferences. :param generation: Path standard generation to use. Defaults to ``LocalFileStorageManager.CORE_V18``, which is the current generation of paths. :return: Path as string """ if hostname is None: raise TankError( "Cannot compute path for local site specific storage - no PTR hostname specified!" ) # get site only; https://www.FOO.com:8080 -> www.foo.com base_url = urllib.parse.urlparse(hostname).netloc.split(":")[0].lower() if generation > cls.CORE_V17: # for 0.18, in order to apply further shortcuts to avoid hitting # MAX_PATH on windows, strip shotgunstudio.com from all # hosted sites # # mysite.shotgunstudio.com -> mysite # shotgun.internal.int -> shotgun.internal.int # base_url = base_url.replace(".shotgunstudio.com", "").replace( ".shotgrid.autodesk.com", "" ) return os.path.join(cls.get_global_root(path_type, generation), base_url)
[docs] @classmethod def get_configuration_root( cls, hostname, project_id, plugin_id, pipeline_config_id, path_type, generation=CORE_V18, ): """ Returns the storage root for any data that is project and config specific. - A well defined project id should always be passed. Passing None as the project id indicates that the *site* configuration, a special toolkit configuration that represents the non-project state in Shotgun. - Configurations that have a pipeline configuration in Shotgun should pass in a pipeline configuration id. When a pipeline configuration is not registered in Shotgun, this value should be None. - If the configuration has been bootstrapped or has a known plugin id, this should be specified via the plugin id parameter. For more details, see :meth:`LocalFileStorageManager.get_global_root`. Examples of paths that will be generated: - Site config: ``ROOT/shotgunsite/p0`` - Project 123, config 33: ``ROOT/shotgunsite/p123c33`` - project 123, no config, plugin id review.rv: ``ROOT/shotgunsite/p123.review.rv`` .. note:: This method does not ensure that the folder exists. :param hostname: Shotgun hostname as string, e.g. 'https://foo.shotgunstudio.com' :param project_id: Shotgun project id as integer. For the site config, this should be None. :param plugin_id: Plugin id string to identify the scope for a particular plugin or integration. For more information, see :meth:`~sgtk.bootstrap.ToolkitManager.plugin_id`. For non-plugin based toolkit projects, this value is None. :param pipeline_config_id: Shotgun pipeline config id. None for bootstraped configs. :param path_type: Type of path to return. One of ``LocalFileStorageManager.LOGGING``, ``LocalFileStorageManager.CACHE``, ``LocalFileStorageManager.PERSISTENT``, where logging is a path where log- and debug related data should be stored, cache is a location intended for cache data, e.g. data that can be deleted without affecting the state of execution, and persistent is a location intended for data that is meant to be persist. This includes things like settings and preferences. :param generation: Path standard generation to use. Defaults to ``LocalFileStorageManager.CORE_V18``, which is the current generation of paths. :return: Path as string """ if generation == cls.CORE_V17: # in order to be backwards compatible with pre-0.18 cache locations, # handle the site configuration (e.g. when project id is None) # as project id zero. if project_id is None: project_id = 0 # older paths are of the form root/mysite.shotgunstudio.com/project_123/config_123 return os.path.join( cls.get_site_root(hostname, path_type, generation), "project_%s" % project_id, "config_%s" % pipeline_config_id, ) else: # new paths are on the form # project 123, config 33: root/mysite/p123c33 # project 123 with plugin id: root/mysite/p123.review.rv # site project: root/mysite/site pc_suffix = "" if pipeline_config_id and not plugin_id: # a config that has a shotgun counterpart pc_suffix = "c%d" % pipeline_config_id elif plugin_id and not pipeline_config_id: # no pc id but instead an plugin id string pc_suffix = ".%s" % filesystem.create_valid_filename(plugin_id) elif plugin_id and pipeline_config_id: pc_suffix = "c%d.%s" % ( pipeline_config_id, filesystem.create_valid_filename(plugin_id), ) else: # No pipeline config id nor plugin id which is possible for caching # at the site level. pc_suffix = "" if project_id is None: # site config project_config_folder = "site%s" % pc_suffix else: project_config_folder = "p%d%s" % (project_id, pc_suffix) return os.path.join( cls.get_site_root(hostname, path_type, generation), project_config_folder, )