Source code for ftrack_api.accessor.server

# :coding: utf-8
# :copyright: Copyright (c) 2015 ftrack

import os
import hashlib
import base64
import json

import requests

from .base import Accessor
from ..data import String
import ftrack_api.exception
import ftrack_api.symbol


[docs]class ServerFile(String): """Representation of a server file."""
[docs] def __init__(self, resource_identifier, session, mode="rb"): """Initialise file.""" self.mode = mode self.resource_identifier = resource_identifier self._session = session self._has_read = False super(ServerFile, self).__init__()
[docs] def flush(self): """Flush all changes.""" super(ServerFile, self).flush() if self.mode == "wb": self._write()
[docs] def read(self, limit=None): """Read file.""" if not self._has_read: self._read() self._has_read = True return super(ServerFile, self).read(limit)
def _read(self): """Read all remote content from key into wrapped_file.""" position = self.tell() self.seek(0) response = requests.get( "{0}/component/get".format(self._session.server_url), params={ "id": self.resource_identifier, "username": self._session.api_user, "apiKey": self._session.api_key, }, stream=True, ) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise ftrack_api.exception.AccessorOperationFailedError( "Failed to read data: {0}.".format(error) ) for block in response.iter_content(ftrack_api.symbol.CHUNK_SIZE): self.wrapped_file.write(block) self.flush() self.seek(position) def _write(self): """Write current data to remote key.""" position = self.tell() self.seek(0) # Retrieve component from cache to construct a filename. component = self._session.get("FileComponent", self.resource_identifier) if not component: raise ftrack_api.exception.AccessorOperationFailedError( "Unable to retrieve component with id: {0}.".format( self.resource_identifier ) ) # Construct a name from component name and file_type. name = component["name"] if component["file_type"]: name = "{0}.{1}".format(name, component["file_type"].lstrip(".")) try: metadata = self._session.get_upload_metadata( component_id=self.resource_identifier, file_name=name, file_size=self._get_size(), checksum=self._compute_checksum(), ) except Exception as error: raise ftrack_api.exception.AccessorOperationFailedError( "Failed to get put metadata: {0}.".format(error) ) # Ensure at beginning of file before put. self.seek(0) # Put the file based on the metadata. response = requests.put( metadata["url"], data=self.wrapped_file, headers=metadata["headers"] ) try: response.raise_for_status() except requests.exceptions.HTTPError as error: raise ftrack_api.exception.AccessorOperationFailedError( "Failed to put file to server: {0}.".format(error) ) self.seek(position) def _get_size(self): """Return size of file in bytes.""" position = self.tell() self.seek(0, os.SEEK_END) length = self.tell() self.seek(position) return length def _compute_checksum(self): """Return checksum for file.""" fp = self.wrapped_file buf_size = ftrack_api.symbol.CHUNK_SIZE hash_obj = hashlib.md5() spos = fp.tell() s = fp.read(buf_size) while s: hash_obj.update(s) s = fp.read(buf_size) base64_digest = base64.encodebytes(hash_obj.digest()).decode("utf-8") if base64_digest[-1] == "\n": base64_digest = base64_digest[0:-1] fp.seek(spos) return base64_digest
class _ServerAccessor(Accessor): """Provide server location access.""" def __init__(self, session, **kw): """Initialise location accessor.""" super(_ServerAccessor, self).__init__(**kw) self._session = session def open(self, resource_identifier, mode="rb"): """Return :py:class:`~ftrack_api.Data` for *resource_identifier*.""" return ServerFile(resource_identifier, session=self._session, mode=mode) def remove(self, resourceIdentifier): """Remove *resourceIdentifier*.""" response = requests.get( "{0}/component/remove".format(self._session.server_url), params={ "id": resourceIdentifier, "username": self._session.api_user, "apiKey": self._session.api_key, }, ) if response.status_code != 200: raise ftrack_api.exception.AccessorOperationFailedError( "Failed to remove file." ) def get_container(self, resource_identifier): """Return resource_identifier of container for *resource_identifier*.""" return None def make_container(self, resource_identifier, recursive=True): """Make a container at *resource_identifier*.""" def list(self, resource_identifier): """Return list of entries in *resource_identifier* container.""" raise NotImplementedError() def exists(self, resource_identifier): """Return if *resource_identifier* is valid and exists in location.""" return False def is_file(self, resource_identifier): """Return whether *resource_identifier* refers to a file.""" raise NotImplementedError() def is_container(self, resource_identifier): """Return whether *resource_identifier* refers to a container.""" raise NotImplementedError() def is_sequence(self, resource_identifier): """Return whether *resource_identifier* refers to a file sequence.""" raise NotImplementedError() def get_url(self, resource_identifier): """Return url for *resource_identifier*.""" url_string = ( "{url}/component/get?id={id}&username={username}" "&apiKey={apiKey}" ) return url_string.format( url=self._session.server_url, id=resource_identifier, username=self._session.api_user, apiKey=self._session.api_key, ) def get_thumbnail_url(self, resource_identifier, size=None): """Return thumbnail url for *resource_identifier*. Optionally, specify *size* to constrain the downscaled image to size x size pixels. """ url_string = ( "{url}/component/thumbnail?id={id}&username={username}" "&apiKey={apiKey}" ) url = url_string.format( url=self._session.server_url, id=resource_identifier, username=self._session.api_user, apiKey=self._session.api_key, ) if size: url += "&size={0}".format(size) return url