class Common(object):
  XIAOMI_HEADER_PREFIX = "x-xiaomi-"
  XIAOMI_HEADER_DATE = "x-xiaomi-date"

  GALAXY_ACCESS_KEY_ID = "GalaxyAccessKeyId"
  SIGNATURE = "Signature"
  EXPIRES = "Expires"

  AUTHORIZATION = "authorization"
  CONTENT_MD5 = "content-md5"
  CONTENT_TYPE = "content-type"
  DATE = "date"
  RANGE = 'range'

  REQUEST_TIME_LIMIT = 900000  # 15min

  CACHE_CONTROL = "cache-control"
  CONTENT_ENCODING = "content-encoding"
  CONTENT_LENGTH = "content-length"
  LAST_MODIFIED = "last-modified"
  LAST_CHECKED = "last-checked"
  UPLOAD_TIME = "upload-time"
  CONTENT_RANGE = "content-range"
  EXPIRATION_TIME = "expiration-time"
  OBJECT_OWNER_ID = "object-owner-id"
  FILE_MODE = "file-mode"
  MULITPART_UPLOAD_MODE = XIAOMI_HEADER_PREFIX + "multipart-upload-mode"
  PREVIOUS_VERSION_ID = "pre-version-id"
  SERVER_SIDE_ENCRYPTION = XIAOMI_HEADER_PREFIX + "server-side-encryption"
  STORAGE_CLASS = XIAOMI_HEADER_PREFIX + "storage-class"
  ONGOING_RESTORE = XIAOMI_HEADER_PREFIX + "ongoing-restore"
  RESTORE_EXPIRY_DATE = XIAOMI_HEADER_PREFIX + "restore-expiry";

  DEFAULT_FDS_SERVICE_BASE_URI = "http://files.fds.api.xiaomi.com/"
  DEFAULT_CDN_SERVICE_URI = "http://cdn.fds.api.xiaomi.com/" class Signer(AuthBase):
  '''
  The signer class used to sign the request.
  '''

  def __init__(self, app_key, app_secret, service_url=None):
    if service_url:
      self._service_base_url = service_url
    self._app_key = str(app_key)
    self._app_secret = str(app_secret)

  def __call__(self, request):
    request.headers[Common.DATE] = formatdate(timeval=None, localtime=False,
                                              usegmt=True)
    signature = self._sign_to_base64(request.method, request.headers,
                                     unquote(request.url), self._app_secret)
    request.headers[Common.AUTHORIZATION] = 'Galaxy-V2 %s:%s' % (
      self._app_key, signature)
    return request

  def _sign(self, method, headers, url, app_secret):
    '''
    Sign the specified http request.
    :param method:     The request method to sign
    :param headers:    The request headers to sign
    :param url:        The request uri to sign
    :param app_secret: The secret used to sign the request
    :return: The signed result, aka the signature
    '''
    string_to_sign = self._construct_string_to_sign(method, headers, url)
    if IS_PY3:
      if isinstance(app_secret, str):
        app_secret = app_secret.encode(encoding='utf-8')
      if isinstance(string_to_sign, str):
        string_to_sign = string_to_sign.encode(encoding='utf-8')
    digest = hmac.new(app_secret, string_to_sign, digestmod=sha1)
    return digest.digest()

  def _sign_to_base64(self, method, headers, url, app_secret):
    '''
    Sign the specified request to base64 encoded result.
    :param method:     The request method to sign
    :param headers:    The request headers to sign
    :param url:        The request uri to sign
    :param app_secret: The secret used to sign the request
    :return: The signed result, aka the signature
    '''
    signature = self._sign(method, headers, url, app_secret)
    if IS_PY3:
      return base64.encodebytes(signature).decode(encoding='utf-8').strip()
    else:
      return base64.encodestring(signature).strip()

  def _construct_string_to_sign(self, http_method, http_headers, uri):
    '''
    Construct the string used to sign the request.
    '''
    result = str()
    result += '%s\n' % http_method
    result += '%s\n' % self._get_header_value(http_headers,
                                              Common.CONTENT_MD5)
    result += '%s\n' % self._get_header_value(http_headers,
                                              Common.CONTENT_TYPE)
    expires = self._get_expires(uri)

    if expires > 0:
      result += '%s\n' % expires
    else:
      xiaomi_date = self._get_header_value(http_headers,
                                           Common.XIAOMI_HEADER_DATE)
      date = str()
      if len(xiaomi_date) == 0:
        date = self._get_header_value(http_headers, Common.DATE)
      result += '%s\n' % date

    result += '%s' % self._canonicalize_xiaomi_headers(http_headers)
    result += '%s' % self._canonicalize_resource(uri)
    return result

  def _get_header_value(self, http_headers, name):
    if http_headers is not None and name in http_headers:
      value = http_headers[name]
      if type(value) is list:
        return http_headers[name][0]
      else:
        return value
    return ""

  def _canonicalize_xiaomi_headers(self, http_headers):
    if http_headers is None or len(http_headers) == 0:
      return ''

    canonicalized_headers = dict()
    for key in http_headers:
      lower_key = key.lower()
      try:
        lower_key = lower_key.decode('utf-8')
      except:
        pass

      if http_headers[key] and lower_key.startswith(Common.XIAOMI_HEADER_PREFIX):
        if type(http_headers[key]) != str:
          canonicalized_headers[lower_key] = str()
          i = 0
          for k in http_headers[key]:
            canonicalized_headers[lower_key] += '%s' % (k.strip())
            i += 1
            if i < len(http_headers[key]):
              canonicalized_headers[lower_key] += ','
        else:
          canonicalized_headers[lower_key] = http_headers[key].strip()

    result = ""
    for key in sorted(canonicalized_headers.keys()):
      values = canonicalized_headers[key]
      result += '%s:%s\n' % (key, values)
    return result

  def _canonicalize_resource(self, uri):
    result = ""
    parsed_url = urlparse(uri)
    result += '%s' % parsed_url.path
    query_args = parsed_url.query.split('&')

    i = 0
    for query in sorted(query_args):
      key = query.split('=')
      if key[0] in SubResource.get_all_subresource():
        if i == 0:
          result += '?'
        else:
          result += '&'
        if len(key) == 1:
          result += '%s' % key[0]
        else:
          result += '%s=%s' % (key[0], key[1])
        i += 1
    return result

  def _get_expires(self, uri):
    parsed_url = urlparse(uri)
    query_args = sorted(parsed_url.query.split('&'))
    for query in query_args:
      key = query.split('=')[0]
      if key == Common.EXPIRES:
        return int(query.split('=')[1])
    return 0 class FDSClientConfiguration(object):
  URI_HTTP = 'http://'

  URI_HTTPS = 'https://'

  URI_CDN = 'cdn'

  URI_SUFFIX = 'fds.api.xiaomi.com'

  URI_NET_SUFFIX = 'fds.api.xiaomi.net'

  URI_CDN_SUFFIX = 'fds.api.mi-img.com'

  def __init__(self, region_name='cnbj0',
               enable_cdn_for_download=True,
               enable_cdn_for_upload=False,
               enable_https=True,
               threshold_size=100 * 1024 * 1024,
               part_size=50 * 1024 * 1024,
               timeout=30,
               max_retries=3,
               endpoint=None):
    """
    :param region_name:
    :param enable_cdn_for_download:
    :param enable_cdn_for_upload:
    :param enable_https:
    :param timeout: connection and read timeout (seconds)
    :param max_retries:
    """
    if region_name is None:
      self._region_name = 'cnbj0'
    else:
      self._region_name = region_name
    self._enable_cdn_for_download = enable_cdn_for_download
    self._enable_cdn_for_upload = enable_cdn_for_upload
    self._enable_https = enable_https
    self._enable_md5_calculate = False
    self._timeout = timeout
    self._max_retries = max_retries
    self._debug = False
    if endpoint is None:
      endpoint = self._region_name + '.' + self.URI_SUFFIX

    self.set_endpoint(endpoint)

    self._threshold_size = threshold_size
    self._part_size = part_size

  def get_threshold_size(self):
    return self._threshold_size

  def set_threshold_size(self, threshold_size):
    self._threshold_size = threshold_size

  def get_part_size(self):
    return self._part_size

  def set_part_size(self, part):
    self._part_size = part

  @property
  def debug(self):
    return self._debug

  @debug.setter
  def debug(self, debug):
    self._debug = debug

  @property
  def enable_md5_calculate(self):
    return self._enable_md5_calculate

  @enable_md5_calculate.setter
  def enable_md5_calculate(self, enable):
    self._enable_md5_calculate = enable

  @property
  def timeout(self):
    return self._timeout

  @timeout.setter
  def timeout(self, timeout):
    self._timeout = timeout

  @property
  def max_retries(self):
    return self._max_retries

  @max_retries.setter
  def max_retries(self, max_retries):
    self._max_retries = max_retries

  def set_endpoint(self, endpoint):
    assert endpoint.endswith(self.URI_SUFFIX) or endpoint.endswith(self.URI_NET_SUFFIX)

    self._endpoint = endpoint
    suffix = self._endpoint[-19:]
    i = self._endpoint.index(suffix)
    self._region_name = self._endpoint[0:i]
    self._cdn_endpoint = self.URI_CDN + '.' + self._region_name + '.' + self.URI_CDN_SUFFIX

  def get_download_base_uri(self):
    return self._build_base_uri(self._enable_cdn_for_download)

  def get_upload_base_uri(self):
    return self._build_base_uri(self._enable_cdn_for_upload)

  def get_base_uri(self):
    return self._build_base_uri(False)

  def get_cdn_base_uri(self):
    return self._build_base_uri(True)

  def _build_base_uri(self, enable_cdn):
    base_uri = str()
    if self._enable_https:
      base_uri += self.URI_HTTPS
    else:
      base_uri += self.URI_HTTP

    if enable_cdn:
      base_uri += self._cdn_endpoint
    else:
      base_uri += self._endpoint

    base_uri += '/'
    return base_uri class FDSRequest:
  def __init__(self, timeout, max_retries):
    self._max_retries = max_retries
    self._timeout = timeout

  def request(self, method, url, kwargs):
    '''
    Constructs a specified session and sends quest.
    Returns :class:`Response <Response>` object.
    '''

    kwargs.setdefault('timeout', self._timeout)
    session = requests.Session()
    session.mount("http://", HTTPAdapter(max_retries=self._max_retries))
    session.mount("https://", HTTPAdapter(max_retries=self._max_retries))
    response = session.request(method=method, url=url, **kwargs)
    # By explicitly closing the session, we avoid leaving sockets open which
    # can trigger a ResourceWarning in some cases, and look like a memory leak
    # in others.
    session.close()
    return response

  def get(self, url, **kwargs):
    kwargs.setdefault('allow_redirects', True)
    return self.request('get', url, kwargs)

  def options(self, url, **kwargs):
    kwargs.setdefault('allow_redirects', True)
    return self.request('options', url, kwargs)

  def head(self, url, **kwargs):
    kwargs.setdefault('allow_redirects', False)
    return self.request('head', url, kwargs)

  def post(self, url, data=None, json=None, **kwargs):
    kwargs['data'] = data
    kwargs['json'] = json
    return self.request('post', url, kwargs)

  def put(self, url, data=None, **kwargs):
    kwargs['data'] = data
    return self.request('put', url, kwargs)

  def patch(self, url, data=None, **kwargs):
    kwargs['data'] = data
    return self.request('patch', url, kwargs)

  def delete(self, url, **kwargs):
    return self.request('delete', url, kwargs) class GalaxyFDSClientException(Error):
  def __init__(self, message):
    self.message = message

  def __str__(self):
    if self.message:
      return super.__str__(self) + ": " + str(self.message)
    return super.__str__(self) class AccessControlPolicy(dict):
  '''
  The access control policy class.
  '''

  def __init__(self, json):
    '''
    Construct access control policy object from json.
    '''
    if json is not None:
      if 'owner' in json.keys():
        self.owner = json['owner']
      else:
        self.owner = None
      if 'accessControlList' in json.keys():
        self.access_control_list = json['accessControlList']
      else:
        self.access_control_list = None
    else:
      raise GalaxyFDSClientException("Json data cannot be None")

  @property
  def owner(self):
    return self.owner

  @owner.setter
  def owner(self, owner):
    self['owner'] = owner

  @property
  def access_control_list(self):
    return self.access_control_list

  @access_control_list.setter
  def access_control_list(self, access_control_list):
    self['accessControlList'] = access_control_list class FDSBucket(object):
  """
  The FDS bucket class.
  """

  def __init__(self, bucket_name, owner=None):
    self.bucket_name = bucket_name
    self.owner = owner
    self.create_date = None

  def __str__(self):
    return self.bucket_name

  @property
  def owner(self):
    return self._owner

  @owner.setter
  def owner(self, json):
    if isinstance(json, dict):
      self._owner = Owner().from_json(json)
    else:
      self._owner = json class FDSObject(object):
  '''
  The FDS Object class.
  '''

  def __init__(self):
    self.summary = None
    self.metadata = None
    self.stream = None

  def get_next_chunk_as_string(self, encoding="UTF-8"):
    if IS_PY3:
      return next(self.stream).decode(encoding=encoding)
    else:
      return self.stream.next() class FDSObjectListing(dict):
  '''
  The FDS Object Listing class.
  '''

  def __init__(self, json):
    dict.__init__(self, json)
    self._objects = []
    for obj in self['objects']:
      summary = FDSObjectSummary()
      summary.bucket_name = self['name']
      summary.object_name = obj['name']
      summary.owner = Owner().from_json(obj['owner'])
      summary.size = obj['size']
      self._objects.append(summary)

  def __str__(self):
    return str(map(lambda x: x.object_name, self._objects))

  @property
  def prefix(self):
    return self['prefix']

  @prefix.setter
  def prefix(self, prefix):
    self['prefix'] = prefix

  @property
  def delimiter(self):
    return self['delimiter'];

  @delimiter.setter
  def delimiter(self, delimiter):
    self['delimiter'] = delimiter

  @property
  def marker(self):
    return self['marker']

  @marker.setter
  def marker(self, marker):
    self['marker'] = marker

  @property
  def next_marker(self):
    return self['nextMarker']

  @next_marker.setter
  def next_marker(self, next_marker):
    self['nextMarker'] = next_marker

  @property
  def bucket_name(self):
    return self['name']

  @bucket_name.setter
  def bucket_name(self, bucket_name):
    self['name'] = bucket_name

  @property
  def delimiter(self):
    return self['delimiter']

  @delimiter.setter
  def delimiter(self, delimiter):
    self['delimiter'] = delimiter

  @property
  def max_keys(self):
    return self['maxKeys']

  @max_keys.setter
  def max_keys(self, max_keys):
    self['maxKeys'] = max_keys

  @property
  def objects(self):
    return self._objects

  @objects.setter
  def objects(self, objects):
    objs = []
    for x in objects:
      if isinstance(x, FDSObjectSummary):
        raise TypeError("Parameter should be a list of FDSObjectSummary")
      objs.append(x)
    self._objects = objs

  @property
  def common_prefixes(self):
    return self['commonPrefixes']

  @common_prefixes.setter
  def common_prefixes(self, common_prefixes):
    self['commonPrefixes'] = common_prefixes

  @property
  def is_truncated(self):
    return self['truncated']

  @is_truncated.setter
  def is_truncated(self, is_truncated):
    self['truncated'] = is_truncated class FDSObjectMetadata(object):
  """
  The FDS object metadata class.
  """
  USER_DEFINED_METADATA_PREFIX = "x-xiaomi-meta-"

  PRE_DEFINED_METADATA = [
    Common.CACHE_CONTROL,
    Common.CONTENT_ENCODING,
    Common.CONTENT_LENGTH,
    Common.CONTENT_RANGE,
    Common.LAST_MODIFIED,
    Common.CONTENT_MD5,
    Common.CONTENT_TYPE,
    Common.LAST_CHECKED,
    Common.UPLOAD_TIME,
    Common.EXPIRATION_TIME,
    Common.OBJECT_OWNER_ID,
    Common.FILE_MODE,
    Common.MULITPART_UPLOAD_MODE,
    Common.PREVIOUS_VERSION_ID,
    Common.SERVER_SIDE_ENCRYPTION,
    Common.STORAGE_CLASS,
    Common.RESTORE_EXPIRY_DATE,
    Common.ONGOING_RESTORE
  ]

  def __init__(self):
    self.metadata = {}

  def add_header(self, key, value):
    self._check_metadata(key)
    self.metadata.update({key: value})

  def add_user_metadata(self, key, value):
    self._check_metadata(key)
    self.metadata.update({key: value})

  def _check_metadata(self, key):
    is_valid = key.startswith(self.USER_DEFINED_METADATA_PREFIX)

    if key in self.PRE_DEFINED_METADATA:
      is_valid = True

    if not is_valid:
      raise GalaxyFDSClientException("Invalid metadata: " + key) class FDSObjectSummary(object):
  """
  The FDS Object Summary class.
  """

  def __init__(self):
    self.bucket_name = None
    self.object_name = None
    self.owner = None
    self.size = None class AccessControlList(object):
  '''
  The access control list class.
  '''

  def __init__(self):
    self.acl = {}

  def add_grant(self, grant):
    self.acl['%s:%s' % (grant.grantee.id, grant.type)] = grant

  def get_grant_list(self):
    grants = []
    for k in self.acl:
      grants.append(self.acl[k])
    return grants

  def is_subset(self, other):
    for k in self.acl:
      if self.acl[k] != other.acl[k]:
        return False
    return True class UserGroups(object):
  '''
  The user groups class.
  '''
  ALL_USERS = 'ALL_USERS'
  AUTHENTICATED_USERS = 'AUTHENTICATED_USERS' class Permission(object):
  """
  The Permission definition class.
  """

  '''
  The READ permission: when it applies to buckets it means allow the grantee to
  list the objects in the bucket; when it applies to objects it means allow the
  grantee to read the object data and metadata.
  '''
  READ = 0x01

  '''
  The WRITE permission: when it applies to buckets it means allow the grantee
  to create, overwrite and delete any object in the bucket; it is not applicable
  for objects.
  '''
  WRITE = 0x02

  '''
  The READ_OBJECT permission: when it applies to buckets it means
  allow the grantee to read any object in the bucket;
  it is not applicable to object.
  '''
  READ_OBJECTS = 0x04

  '''
  The SSO_WRITE permission: when applied to bucket, it means
  users can put objects to the bucket with SSO auth
  it is not applicable to object.
  '''
  SSO_WRITE = 0x08

  '''
  The FULL_CONTROL permission: allows the grantee the READ and WRITE permission
  on the bucket/object.
  '''
  FULL_CONTROL = 0xff

  def __init__(self, value):
    types = IS_PY3 and (str) or (str, unicode)

    if isinstance(value, types):
      value = value.strip().upper()
      if value == 'READ':
        self._value = Permission.READ
      elif value == 'WRITE':
        self._value = Permission.WRITE
      elif value == 'READ_OBJECTS':
        self._value = Permission.READ_OBJECTS
      elif value == 'SSO_WRITE':
        self._value = Permission.SSO_WRITE
      elif value == 'FULL_CONTROL':
        self._value = Permission.FULL_CONTROL
      else:
        raise RuntimeError('Fatal error')
    else:
      self._value = value

  def __eq__(self, other):
    return self._value == other._value

  def to_string(self):
    value = self.get_value()
    if value == Permission.READ:
      return 'READ'
    elif value == Permission.WRITE:
      return 'WRITE'
    elif value == Permission.READ_OBJECTS:
      return 'READ_OBJECTS'
    elif value == Permission.SSO_WRITE:
      return 'SSO_WRITE'
    elif value == Permission.FULL_CONTROL:
      return 'FULL_CONTROL'
    else:
      raise RuntimeError('Fatal error')

  def get_value(self):
    return self._value class GrantType(object):
  '''
  The grant type class.
  '''
  USER = 'USER'
  GROUP = 'GROUP' class Grant(dict):
  '''
  The grant class.
  '''

  def __init__(self, grantee, permission):
    self.grantee = grantee
    self.type = GrantType.USER
    self.permission = permission

  @property
  def permission(self):
    return self['permission']

  @permission.setter
  def permission(self, permission):
    self['permission'] = Permission(permission)

  @property
  def grantee(self):
    return self['grantee']

  @grantee.setter
  def grantee(self, grantee):
    self['grantee'] = grantee

  @property
  def type(self):
    return self['type']

  @type.setter
  def type(self, type):
    self['type'] = type class Grantee(dict):
  '''
  The grantee definition class.
  '''

  def __init__(self, id):
    self.id = id

  @property
  def display_name(self):
    return self['displayName']

  @display_name.setter
  def display_name(self, display_name):
    self['displayName'] = display_name

  @property
  def id(self):
    return self['id']

  @id.setter
  def id(self, id):
    self['id'] = id class Owner(dict):
  '''
  The owner definition class.
  '''

  @staticmethod
  def from_json(json):
    if json != '':
      owner = Owner()
      if 'id' in json.keys():
        owner.id = json['id']
      if 'displayName' in json.keys():
        owner.display_name = json['displayName']
      return owner
    return None

  @property
  def id(self):
    return self.id

  @id.setter
  def id(self, id):
    self['id'] = id

  @property
  def display_name(self):
    return self.display_name

  @display_name.setter
  def display_name(self, display_name):
    self['displayName'] = display_name class PutObjectResult(object):
  '''
  The Put Object Result class.
  '''

  def __init__(self, json):
    self.bucket_name = json['bucketName']
    self.object_name = json['objectName']
    self.access_key_id = json['accessKeyId']
    self.signature = json['signature']
    self.expires = json['expires']
    if 'previousVersionId' in json:
      self.previous_version_id = json['previousVersionId']
    else:
      self.previous_version_id = None class SubResource(object):
  '''
  The sub-resource class.
  '''
  ACL = "acl"
  QUOTA = "quota"
  UPLOADS = "uploads"
  PART_NUMBER = "partNumber"
  UPLOAD_ID = "uploadId"
  STORAGE_ACCESS_TOKEN = "storageAccessToken"
  METADATA = "metadata"

  @staticmethod
  def get_all_subresource():
    return [SubResource.ACL,
            SubResource.QUOTA,
            SubResource.UPLOADS,
            SubResource.PART_NUMBER,
            SubResource.UPLOAD_ID,
            SubResource.STORAGE_ACCESS_TOKEN,
            SubResource.METADATA
            ] class InitMultipartUploadResult(object):
  """
  The Init Multipart Upload Result class:
  """

  def __init__(self, json):
    self.bucket_name = json['bucketName']
    self.object_name = json['objectName']
    self.upload_id = json['uploadId'] class UploadPartResult(dict):
  """
  The Upload Part Result class.
  """

  def __init__(self, json):
    self.partNumber = json['partNumber']
    self.etag = json['etag']
    self.partSize = json['partSize']

  @property
  def partNumber(self):
    return self.partNumber

  @partNumber.setter
  def partNumber(self, part_number):
    self['partNumber'] = part_number

  @property
  def etag(self):
    return self.etag

  @etag.setter
  def etag(self, etag):
    self['etag'] = etag

  @property
  def partSize(self):
    return self.partSize

  @partSize.setter
  def partSize(self, part_size):
    self['partSize'] = part_size class FDSLifecycleConfig(dict):
  '''
  lifecycle config like this:

  {
   "rules": [
     {
       "id": 0,
       "enabled": true,
       "prefix": "log",
       "actions": {
         "nonCurrentVersionExpiration": {
           "days": 7
         },
         "expiration": {
           "days":30
         }
       }
     },
     {
       "enabled": true,
       "prefix": "images",
       "actions": {
         "nonCurrentVersionExpiration": {
           "days": 7
         },
         "expiration": {
           "days":30
         },
         "abortIncompleteMultipartUpload": {
           "days":7
         }
       }
     }
   ]
 }
  '''

  def __init__(self, json={}):
    dict.__init__(self, json)
    self._rules = []
    for rule in self.get('rules', []):
      self._rules.append(FDSLifecycleRule(rule))
    self['rules'] = self._rules

  @property
  def rules(self):
    return self._rules

  def get_rule_by_prefix(self, prefix):
    for rule in self.rules:
      if rule.prefix == prefix:
        return rule
    return None

  def get_rule_by_object_name(self, object_name, enabled_rule_only=False):
    '''
    Get rule by object name
    :param object_name:
    :param enabled_rule_only:
    :return: Only one rule will return if more than one rules matched.
    '''
    for rule in self.rules:
      if object_name.startswith(rule.prefix) and (rule.enabled or not enabled_rule_only):
        return rule
    return None class FDSLifecycleRule(dict):
  def __init__(self, json={}):
    dict.__init__(self, json)
    self._actions = {}
    for name, action in self.get('actions', {}).items():
      if name == 'abortIncompleteMultipartUpload':
        self._actions[name] = FDSAbortIncompleteMultipartUpload(action)
      elif name == 'expiration':
        self._actions[name] = FDSExpiration(action)
      elif name == 'nonCurrentVersionExpiration':
        self._actions[name] = FDSNonCurrentVersionExpiration(action)
      elif name == 'lifeCycleStorageClass':
        self._actions[name] = FDSLifecycleStorageClass(action)
      else:
        raise GalaxyFDSClientException("invalid action type: " + name)
    self['actions'] = self._actions

  @property
  def id(self):
    return self.get('id', None)

  @property
  def enabled(self):
    return self.get('enabled', False)

  @enabled.setter
  def enabled(self, enabled):
    self['enabled'] = enabled

  @property
  def prefix(self):
    return self.get('prefix', None)

  @prefix.setter
  def prefix(self, prefix):
    self['prefix'] = prefix

  @property
  def actions(self):
    return self._actions

  def update_action(self, action):
    self.actions[action.name] = action class FDSCORSConfig(dict):
  '''
  cors config like this:

  {
   "rules": [
     {
       "id": "0",
       "allowOrigin":"*.example.com"
     },
     {
       "id": "1",
       "allowOrigin":"*"
     }
   ]
  }
  '''

  def __init__(self, json={}):
    dict.__init__(self, json)
    self._rules = []
    for rule in self.get('rules', []):
      self._rules.append(FDSCORSRule(rule))
    self['rules'] = self._rules

  @property
  def rules(self):
    return self._rules

  def get_rule_by_id(self, id):
    for rule in self.rules:
      if rule.id == id:
        return rule
    return None class FDSCORSRule(dict):
  def __init__(self, json = {}):
    dict.__init__(self, json)

  @property
  def id(self):
    return self.get('id', None)

  @id.setter
  def id(self, id):
    self['id'] = id

  @property
  def allowOrigin(self):
    return self.get('allowOrigin', None)

  @allowOrigin.setter
  def allowOrigin(self, allowOrigin):
    self['allowOrigin'] = allowOrigin class CopyObjectResult(PutObjectResult):
  def from_put_object_result(self, put_object_result):
    self.access_key_id = put_object_result.access_key_id
    self.bucket_name = put_object_result.bucket_name
    self.expires = put_object_result.expires
    self.object_name = put_object_result.object_name
    self.previous_version_id = put_object_result.previous_version_id
    self.signature = put_object_result.signature
    return self class TimestampAntiStealingLinkConfig(dict):
  def __init__(self, json):
    if json is not None:
      if 'enabled' in json.keys():
        self.enabled = json['enabled']
      else:
        self.enabled = None
      if 'primaryKey' in json.keys():
        self.primary_key = json['primaryKey']
      else:
        self.primary_key = None
      if 'secondaryKey' in json.keys():
        self.secondary_key = json['secondaryKey']
      else:
        self.secondary_key = None
    else:
      raise GalaxyFDSClientException("Json data cannot be None")

  @property
  def enabled(self):
    return self['enabled']

  @enabled.setter
  def enabled(self, enabled):
    self['enabled'] = enabled

  @property
  def primary_key(self):
    return self['primaryKey']

  @primary_key.setter
  def primary_key(self, primary_key):
    self['primaryKey'] = primary_key

  @property
  def secondary_key(self):
    return self['secondaryKey']

  @secondary_key.setter
  def secondary_key(self, secondary_key):
    self['secondaryKey'] = secondary_key class ListDomainMappingsResult(object):
  '''
  The List Domain Mappings Result class:
  '''

  def __init__(self, json):
    if json is not None:
      if 'domainMappings' in json.keys():
        self.domain_mappings = json['domainMappings']
      else:
        self.domain_mappings = None
    else:
      raise GalaxyFDSClientException("Json data cannot be None") class AccessLogConfig(dict):
  '''
  The Access Log Config Class:
  '''

  def __init__(self, json):
    if 'bucketName' in json.keys():
      self.bucketName = json['bucketName']
    if 'enabled' in json.keys():
      self.enabled = json['enabled']
    if 'logBucketName' in json.keys():
      self.logBucketName = json['logBucketName']
    if 'logPrefix' in json.keys():
      self.logPrefix = json['logPrefix']
    if 'cdnEnabled' in json.keys():
      self.cdnEnabled = json['cdnEnabled']
    if 'cdnLogPrefix' in json.keys():
      self.cdnLogPrefix = json['cdnLogPrefix']

  @property
  def cdnEnabled(self):
    return self.cdnEnabled

  @cdnEnabled.setter
  def cdnEnabled(self, cdnEnabled):
    self['cdnEnabled'] = cdnEnabled

  @property
  def logPrefix(self):
    return self.logPrefix

  @logPrefix.setter
  def logPrefix(self, logPrefix):
    self['logPrefix'] = logPrefix

  @property
  def enabled(self):
    return self.enabled

  @enabled.setter
  def enabled(self, enabled):
    self['enabled'] = enabled

  @property
  def logBucketName(self):
    return self.logBucketName

  @logBucketName.setter
  def logBucketName(self, logBucketName):
    self['logBucketName'] = logBucketName

  @property
  def bucketName(self):
    return self.bucketName

  @bucketName.setter
  def bucketName(self, bucketName):
    self['bucketName'] = bucketName

  @property
  def cdnLogPrefix(self):
    return self.cdnLogPrefix

  @cdnLogPrefix.setter
  def cdnLogPrefix(self, cdnLogPrefix):
    self['cdnLogPrefix'] = cdnLogPrefix class FDSStorageClass(Enum):
  Standard = "STANDARD"
  StandardInfrequentAccess = "STANDARD_IA"
  Archive = "ARCHIVE" def uri_to_bucket_and_object(uri):
    if not uri.startswith("fds://"):
        return None, None

    bucket_object_pair = uri[6:].split('/', 1)
    bucket = bucket_object_pair[0]
    object = bucket_object_pair[1]
    return bucket, object def to_json_object(src):
    if isinstance(src, bytes):
        src = src.decode(encoding='utf-8')
    if src:
        return json.loads(src)
    return None class UploadPartResultList(dict):
  """
  The Upload Part Result List class
  """

  def __init__(self, json):
    if json:
      self.uploadPartResultList = json['uploadPartResultList']

  @property
  def uploadPartResultList(self):
    return self.uploadPartResultList

  @uploadPartResultList.setter
  def uploadPartResultList(self, upload_part_result_list):
    self['uploadPartResultList'] = upload_part_result_list
import hashlib
import json
import requests
import os
import sys
import logging
      import traceback
from sys import version_info
  from urllib.parse import quote
  from urllib import quote as _quote
from fds.auth.common import Common
from fds.auth.signature.signer import Signer
from fds.fds_client_configuration import FDSClientConfiguration
from fds.fds_request import FDSRequest
from fds.galaxy_fds_client_exception import GalaxyFDSClientException
from fds.model.access_control_policy import AccessControlPolicy
from fds.model.fds_bucket import FDSBucket
from fds.model.fds_object import FDSObject
from fds.model.fds_object_listing import FDSObjectListing
from fds.model.fds_object_metadata import FDSObjectMetadata
from fds.model.fds_object_summary import FDSObjectSummary
from fds.model.permission import AccessControlList, UserGroups, Permission, \
  GrantType
from fds.model.permission import Grant
from fds.model.permission import Grantee
from fds.model.permission import Owner
from fds.model.put_object_result import PutObjectResult
from fds.model.subresource import SubResource
from fds.model.init_multipart_upload_result import InitMultipartUploadResult
from fds.model.upload_part_result import UploadPartResult
from fds.model.fds_lifecycle import FDSLifecycleConfig, FDSLifecycleRule
from fds.model.fds_cors import FDSCORSConfig, FDSCORSRule
from fds.model.copy_object_result import CopyObjectResult
from fds.model.timestamp_anti_stealing_link_config import TimestampAntiStealingLinkConfig
from fds.model.list_domain_mappings_result import ListDomainMappingsResult
from fds.model.fds_access_log_config import AccessLogConfig
from fds.model.fds_storage_class import FDSStorageClass
from .utils import uri_to_bucket_and_object, to_json_object
from fds.model.upload_part_result_list import UploadPartResultList
from io import BytesIO
from io import IOBase
from io import StringIO
from time import sleep
      "uploadId": upload_id
    })
    if response.status_code != requests.codes.ok:
      headers = ""
      if self._config.debug:
        headers = ' headers=%s' % response.headers
      message = 'Abort multipart upload failed, status=%s, reason=%s%s' % (
        response.status_code, response.content, headers)
      raise GalaxyFDSClientException(message)

  def generate_presigned_uri(self, base_uri, bucket_name, object_name,
                             expiration, http_method="GET", content_type=None, sub_resources=None):
    '''
    Generate a pre-signed uri to share object with the public
    :param base_uri: The base uri of rest server. Use client's default if 'None' pass
    :param bucket_name: The name of the bucket
    :param object_name: The name of the object
    :param expiration: The expiration time of the uri: milliseconds from the Epoch
    :param http_method: The http method used in uri
    :return: The pre-signed uri string
    '''
    if not base_uri or base_uri == '':
      if http_method == 'PUT' or http_method == 'POST':
        base_uri = self._config.get_upload_base_uri()
      elif http_method == 'DELETE':
        base_uri = self._config.get_base_uri()
      else:
        base_uri = self._config.get_download_base_uri()
    if not IS_PY3 and isinstance(base_uri, unicode):
      base_uri = base_uri.encode('utf8')
    try:
      if sub_resources == None:
        uri = '%s%s/%s?%s=%s&%s=%s&' % \
              (base_uri, bucket_name, object_name, \
               Common.GALAXY_ACCESS_KEY_ID, self._auth._app_key, \
               Common.EXPIRES, str(int(expiration)))
      else:
        uri = '%s%s/%s?%s&%s=%s&%s=%s&' % \
              (base_uri, bucket_name, object_name, '&'.join(sub_resources), \
               Common.GALAXY_ACCESS_KEY_ID, self._auth._app_key, \
               Common.EXPIRES, str(int(expiration)))
      headers = None
      if content_type != None and isinstance(content_type, IS_PY3 and str or basestring):
        headers = {Common.CONTENT_TYPE: content_type}
      signature = str(self._auth._sign_to_base64(http_method, headers, uri, \
                                                 self._auth._app_secret))
      if sub_resources == None:
        return '%s%s/%s?%s=%s&%s=%s&%s=%s' % \
               (base_uri, quote(bucket_name), quote(object_name), \
                Common.GALAXY_ACCESS_KEY_ID, self._auth._app_key, \
                Common.EXPIRES, str(int(expiration)), Common.SIGNATURE, signature)
      else:
        return '%s%s/%s?%s&%s=%s&%s=%s&%s=%s' % \
               (base_uri, quote(bucket_name), quote(object_name), '&'.join(sub_resources), \
                Common.GALAXY_ACCESS_KEY_ID, self._auth._app_key, \
                Common.EXPIRES, str(int(expiration)), Common.SIGNATURE, signature)

    except Exception as e:
      traceback.print_exc()
      message = 'Wrong expiration given. ' \
                'Milliseconds since January 1, 1970 should be used. ' + str(e)
      raise GalaxyFDSClientException(message)

  def generate_download_object_uri(self, bucket_name, object_name):
    '''
    Generate a URI for downloading object
    '''
    return '%s%s/%s' % (self._config.get_download_base_uri(), bucket_name,
                        object_name)

  def copy_object(self, src_bucket_name, src_object_name, dst_bucket_name, dst_object_name):
    '''
    Copy src_object_name from src_bucket_name to dst_bucket_name, and rename it to dst_object_name
    :param src_bucket_name: Source bucket name
    :param src_object_name: Source object name
    :param dst_bucket_name: Target bucket name
    :param dst_object_name: Target object name
    :return:
    '''
    uri = '%s%s/%s' % (self._config.get_upload_base_uri(), quote(dst_bucket_name),\
      quote(dst_object_name))

    data = {"srcBucketName": src_bucket_name, "srcObjectName": src_object_name}

    response = self._request.put(uri, data=json.dumps(data, default=lambda x: x.to_string()),
                                 auth=self._auth, params={"cp": "cpparam"})
    if response.status_code == requests.codes.ok:
      return CopyObjectResult(to_json_object(response.content))
    headers = ""
    if self._config.debug:
      headers = ' header=%s' % response.headers
    message = 'Copy object failed, status=%s, reason=%s%s' % (
      response.status_code, response.content, headers)
    raise GalaxyFDSClientException(message)

  def _acp_to_acl(self, acp):
    '''
    Translate AccessControlPolicy to AccessControlList.
    '''
    if acp is not None:
      acl = AccessControlList()
      for item in acp['accessControlList']:
        grantee = item['grantee']
        grant_id = grantee['id']
        permission = item['permission']
        g = Grant(Grantee(grant_id), permission)
        acl.add_grant(g)
      return acl
    return str()

  # def copy_object(self, srcBucketName, srcObjectName, dstBucketName, dstObjectName):
  #   cp_request = FDSCopyObjectRequest(srcBucketName, srcObjectName, dstBucketName, dstObjectName)
  #   return self.copy_object(cp_request)

  def _acl_to_acp(self, acl):
    '''
    Translate AccessControlList to AccessControlPolicy.
    '''
    if acl is not None:
      acp = AccessControlPolicy(dict())

      owner = Owner()