# coding: utf-8

# -------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for
# license information.
# --------------------------------------------------------------------------
import sys
import math
from os import path

from azure.common import AzureHttpError

from ..common._auth import (
    _StorageSharedKeyAuthentication,
    _StorageSASAuthentication,
)
from ..common._common_conversion import (
    _int_to_str,
    _to_str,
    _get_content_md5,
)
from ..common._connection import _ServiceParameters
from ..common._constants import (
    SERVICE_HOST_BASE,
    DEFAULT_PROTOCOL,
    DEV_ACCOUNT_NAME,
)
from ..common._deserialization import (
    _convert_xml_to_service_properties,
    _convert_xml_to_signed_identifiers,
    _parse_metadata,
    _parse_properties,
    _parse_length_from_content_range,
)
from ..common._error import (
    _dont_fail_not_exist,
    _dont_fail_on_exist,
    _validate_not_none,
    _validate_type_bytes,
    _ERROR_VALUE_NEGATIVE,
    _ERROR_STORAGE_MISSING_INFO,
    _ERROR_EMULATOR_DOES_NOT_SUPPORT_FILES,
    _ERROR_PARALLEL_NOT_SEEKABLE,
    _validate_access_policies,
)
from ..common._http import HTTPRequest
from ..common._serialization import (
    _get_request_body,
    _get_data_bytes_only,
    _convert_signed_identifiers_to_xml,
    _convert_service_properties_to_xml,
    _add_metadata_headers,
)
from ..common.models import (
    Services,
    ListGenerator,
    _OperationContext,
)
from .sharedaccesssignature import (
    FileSharedAccessSignature,
)
from ..common.storageclient import StorageClient
from ._deserialization import (
    _convert_xml_to_shares,
    _convert_xml_to_directories_and_files,
    _convert_xml_to_handles,
    _parse_close_handle_response,
    _convert_xml_to_ranges,
    _convert_xml_to_share_stats,
    _parse_file,
    _parse_share,
    _parse_snapshot_share,
    _parse_directory,
)
from ._download_chunking import _download_file_chunks
from ._serialization import (
    _get_path,
    _validate_and_format_range_headers,
)
from ._upload_chunking import _upload_file_chunks
from .models import (
    FileProperties,
)

from ._constants import (
    X_MS_VERSION,
    __version__ as package_version,
)

_SHARE_NOT_FOUND_ERROR_CODE = 'ShareNotFound'
_PARENT_NOT_FOUND_ERROR_CODE = 'ParentNotFound'
_RESOURCE_NOT_FOUND_ERROR_CODE = 'ResourceNotFound'
_RESOURCE_ALREADY_EXISTS_ERROR_CODE = 'ResourceAlreadyExists'
_SHARE_ALREADY_EXISTS_ERROR_CODE = 'ShareAlreadyExists'

_GB = 1024 * 1024 * 1024

if sys.version_info >= (3,):
    from io import BytesIO
else:
    from cStringIO import StringIO as BytesIO


class FileService(StorageClient):
    '''
    The Server Message Block (SMB) protocol is the preferred file share protocol
    used on premise today. The Microsoft Azure File service enables customers to
    leverage the availability and scalability of Azure's Cloud Infrastructure as
    a Service (IaaS) SMB without having to rewrite SMB client applications.

    The Azure File service also offers a compelling alternative to traditional
    Direct Attached Storage (DAS) and Storage Area Network (SAN) solutions, which
    are often complex and expensive to install, configure, and operate.

    :ivar int MAX_SINGLE_GET_SIZE:
        The size of the first range get performed by get_file_to_* methods if
        max_connections is greater than 1. Less data will be returned if the
        file is smaller than this.
    :ivar int MAX_CHUNK_GET_SIZE:
        The size of subsequent range gets performed by get_file_to_* methods if
        max_connections is greater than 1 and the file is larger than MAX_SINGLE_GET_SIZE.
        Less data will be returned if the remainder of the file is smaller than
        this. If this is set to larger than 4MB, content_validation will throw an
        error if enabled. However, if content_validation is not desired a size
        greater than 4MB may be optimal. Setting this below 4MB is not recommended.
    :ivar int MAX_RANGE_SIZE:
        The size of the ranges put by create_file_from_* methods. Smaller ranges
        may be put if there is less data provided. The maximum range size the service
        supports is 4MB.
    '''
    MAX_SINGLE_GET_SIZE = 32 * 1024 * 1024
    MAX_CHUNK_GET_SIZE = 8 * 1024 * 1024
    MAX_RANGE_SIZE = 4 * 1024 * 1024

    def __init__(self, account_name=None, account_key=None, sas_token=None,
                 protocol=DEFAULT_PROTOCOL, endpoint_suffix=SERVICE_HOST_BASE,
                 request_session=None, connection_string=None, socket_timeout=None):
        '''
        :param str account_name:
            The storage account name. This is used to authenticate requests
            signed with an account key and to construct the storage endpoint. It
            is required unless a connection string is given.
        :param str account_key:
            The storage account key. This is used for shared key authentication.
        :param str sas_token:
             A shared access signature token to use to authenticate requests
             instead of the account key. If account key and sas token are both
             specified, account key will be used to sign.
        :param str protocol:
            The protocol to use for requests. Defaults to https.
        :param str endpoint_suffix:
            The host base component of the url, minus the account name. Defaults
            to Azure (core.windows.net). Override this to use the China cloud
            (core.chinacloudapi.cn).
        :param requests.Session request_session:
            The session object to use for http requests.
        :param str connection_string:
            If specified, this will override all other parameters besides
            request session. See
            http://azure.microsoft.com/en-us/documentation/articles/storage-configure-connection-string/
            for the connection string format.
        :param int socket_timeout:
            If specified, this will override the default socket timeout. The timeout specified is in seconds.
            See DEFAULT_SOCKET_TIMEOUT in _constants.py for the default value.
        '''
        service_params = _ServiceParameters.get_service_parameters(
            'file',
            account_name=account_name,
            account_key=account_key,
            sas_token=sas_token,
            protocol=protocol,
            endpoint_suffix=endpoint_suffix,
            request_session=request_session,
            connection_string=connection_string,
            socket_timeout=socket_timeout)

        super(FileService, self).__init__(service_params)

        if self.account_name == DEV_ACCOUNT_NAME:
            raise ValueError(_ERROR_EMULATOR_DOES_NOT_SUPPORT_FILES)

        if self.account_key:
            self.authentication = _StorageSharedKeyAuthentication(
                self.account_name,
                self.account_key,
            )
        elif self.sas_token:
            self.authentication = _StorageSASAuthentication(self.sas_token)
        else:
            raise ValueError(_ERROR_STORAGE_MISSING_INFO)
        self._X_MS_VERSION = X_MS_VERSION
        self._update_user_agent_string(package_version)

    def make_file_url(self, share_name, directory_name, file_name,
                      protocol=None, sas_token=None):
        '''
        Creates the url to access a file.

        :param str share_name:
            Name of share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of file.
        :param str protocol:
            Protocol to use: 'http' or 'https'. If not specified, uses the
            protocol specified when FileService was initialized.
        :param str sas_token:
            Shared access signature token created with
            generate_shared_access_signature.
        :return: file access URL.
        :rtype: str
        '''

        if directory_name is None:
            url = '{}://{}/{}/{}'.format(
                protocol or self.protocol,
                self.primary_endpoint,
                share_name,
                file_name,
            )
        else:
            url = '{}://{}/{}/{}/{}'.format(
                protocol or self.protocol,
                self.primary_endpoint,
                share_name,
                directory_name,
                file_name,
            )

        if sas_token:
            url += '?' + sas_token

        return url

    def generate_account_shared_access_signature(self, resource_types, permission,
                                                 expiry, start=None, ip=None, protocol=None):
        '''
        Generates a shared access signature for the file service.
        Use the returned signature with the sas_token parameter of the FileService.

        :param ResourceTypes resource_types:
            Specifies the resource types that are accessible with the account SAS.
        :param AccountPermissions permission:
            The permissions associated with the shared access signature. The
            user is restricted to operations allowed by the permissions.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has been
            specified in an associated stored access policy.
        :param expiry:
            The time at which the shared access signature becomes invalid.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has
            been specified in an associated stored access policy. Azure will always
            convert values to UTC. If a date is passed in without timezone info, it
            is assumed to be UTC.
        :type expiry: datetime or str
        :param start:
            The time at which the shared access signature becomes valid. If
            omitted, start time for this call is assumed to be the time when the
            storage service receives the request. Azure will always convert values
            to UTC. If a date is passed in without timezone info, it is assumed to
            be UTC.
        :type start: datetime or str
        :param str ip:
            Specifies an IP address or a range of IP addresses from which to accept requests.
            If the IP address from which the request originates does not match the IP address
            or address range specified on the SAS token, the request is not authenticated.
            For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS
            restricts the request to those IP addresses.
        :param str protocol:
            Specifies the protocol permitted for a request made. Possible values are
            both HTTPS and HTTP (https,http) or HTTPS only (https). The default value
            is https,http. Note that HTTP only is not a permitted value.
        :return: A Shared Access Signature (sas) token.
        :rtype: str
        '''
        _validate_not_none('self.account_name', self.account_name)
        _validate_not_none('self.account_key', self.account_key)

        sas = FileSharedAccessSignature(self.account_name, self.account_key)
        return sas.generate_account(Services.FILE, resource_types, permission,
                                    expiry, start=start, ip=ip, protocol=protocol)

    def generate_share_shared_access_signature(self, share_name,
                                               permission=None,
                                               expiry=None,
                                               start=None,
                                               id=None,
                                               ip=None,
                                               protocol=None,
                                               cache_control=None,
                                               content_disposition=None,
                                               content_encoding=None,
                                               content_language=None,
                                               content_type=None):
        '''
        Generates a shared access signature for the share.
        Use the returned signature with the sas_token parameter of FileService.

        :param str share_name:
            Name of share.
        :param SharePermissions permission:
            The permissions associated with the shared access signature. The
            user is restricted to operations allowed by the permissions.
            Permissions must be ordered read, create, write, delete, list.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has been
            specified in an associated stored access policy.
        :param expiry:
            The time at which the shared access signature becomes invalid.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has
            been specified in an associated stored access policy. Azure will always
            convert values to UTC. If a date is passed in without timezone info, it
            is assumed to be UTC.
        :type expiry: datetime or str
        :param start:
            The time at which the shared access signature becomes valid. If
            omitted, start time for this call is assumed to be the time when the
            storage service receives the request. Azure will always convert values
            to UTC. If a date is passed in without timezone info, it is assumed to
            be UTC.
        :type start: datetime or str
        :param str id:
            A unique value up to 64 characters in length that correlates to a
            stored access policy. To create a stored access policy, use :func:`~set_share_acl`.
        :param str ip:
            Specifies an IP address or a range of IP addresses from which to accept requests.
            If the IP address from which the request originates does not match the IP address
            or address range specified on the SAS token, the request is not authenticated.
            For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS
            restricts the request to those IP addresses.
        :param str protocol:
            Specifies the protocol permitted for a request made. Possible values are
            both HTTPS and HTTP (https,http) or HTTPS only (https). The default value
            is https,http. Note that HTTP only is not a permitted value.
        :param str cache_control:
            Response header value for Cache-Control when resource is accessed
            using this shared access signature.
        :param str content_disposition:
            Response header value for Content-Disposition when resource is accessed
            using this shared access signature.
        :param str content_encoding:
            Response header value for Content-Encoding when resource is accessed
            using this shared access signature.
        :param str content_language:
            Response header value for Content-Language when resource is accessed
            using this shared access signature.
        :param str content_type:
            Response header value for Content-Type when resource is accessed
            using this shared access signature.
        :return: A Shared Access Signature (sas) token.
        :rtype: str
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('self.account_name', self.account_name)
        _validate_not_none('self.account_key', self.account_key)

        sas = FileSharedAccessSignature(self.account_name, self.account_key)
        return sas.generate_share(
            share_name,
            permission,
            expiry,
            start=start,
            id=id,
            ip=ip,
            protocol=protocol,
            cache_control=cache_control,
            content_disposition=content_disposition,
            content_encoding=content_encoding,
            content_language=content_language,
            content_type=content_type,
        )

    def generate_file_shared_access_signature(self, share_name,
                                              directory_name=None,
                                              file_name=None,
                                              permission=None,
                                              expiry=None,
                                              start=None,
                                              id=None,
                                              ip=None,
                                              protocol=None,
                                              cache_control=None,
                                              content_disposition=None,
                                              content_encoding=None,
                                              content_language=None,
                                              content_type=None):
        '''
        Generates a shared access signature for the file.
        Use the returned signature with the sas_token parameter of FileService.

        :param str share_name:
            Name of share.
        :param str directory_name:
            Name of directory. SAS tokens cannot be created for directories, so
            this parameter should only be present if file_name is provided.
        :param str file_name:
            Name of file.
        :param FilePermissions permission:
            The permissions associated with the shared access signature. The
            user is restricted to operations allowed by the permissions.
            Permissions must be ordered read, create, write, delete, list.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has been
            specified in an associated stored access policy.
        :param expiry:
            The time at which the shared access signature becomes invalid.
            Required unless an id is given referencing a stored access policy
            which contains this field. This field must be omitted if it has
            been specified in an associated stored access policy. Azure will always
            convert values to UTC. If a date is passed in without timezone info, it
            is assumed to be UTC.
        :type expiry: datetime or str
        :param start:
            The time at which the shared access signature becomes valid. If
            omitted, start time for this call is assumed to be the time when the
            storage service receives the request. Azure will always convert values
            to UTC. If a date is passed in without timezone info, it is assumed to
            be UTC.
        :type start: datetime or str
        :param str id:
            A unique value up to 64 characters in length that correlates to a
            stored access policy. To create a stored access policy, use
            set_file_service_properties.
        :param str ip:
            Specifies an IP address or a range of IP addresses from which to accept requests.
            If the IP address from which the request originates does not match the IP address
            or address range specified on the SAS token, the request is not authenticated.
            For example, specifying sip=168.1.5.65 or sip=168.1.5.60-168.1.5.70 on the SAS
            restricts the request to those IP addresses.
        :param str protocol:
            Specifies the protocol permitted for a request made. Possible values are
            both HTTPS and HTTP (https,http) or HTTPS only (https). The default value
            is https,http. Note that HTTP only is not a permitted value.
        :param str cache_control:
            Response header value for Cache-Control when resource is accessed
            using this shared access signature.
        :param str content_disposition:
            Response header value for Content-Disposition when resource is accessed
            using this shared access signature.
        :param str content_encoding:
            Response header value for Content-Encoding when resource is accessed
            using this shared access signature.
        :param str content_language:
            Response header value for Content-Language when resource is accessed
            using this shared access signature.
        :param str content_type:
            Response header value for Content-Type when resource is accessed
            using this shared access signature.
        :return: A Shared Access Signature (sas) token.
        :rtype: str
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('self.account_name', self.account_name)
        _validate_not_none('self.account_key', self.account_key)

        sas = FileSharedAccessSignature(self.account_name, self.account_key)
        return sas.generate_file(
            share_name,
            directory_name,
            file_name,
            permission,
            expiry,
            start=start,
            id=id,
            ip=ip,
            protocol=protocol,
            cache_control=cache_control,
            content_disposition=content_disposition,
            content_encoding=content_encoding,
            content_language=content_language,
            content_type=content_type,
        )

    def set_file_service_properties(self, hour_metrics=None, minute_metrics=None,
                                    cors=None, timeout=None):
        '''
        Sets the properties of a storage account's File service, including
        Azure Storage Analytics. If an element (ex HourMetrics) is left as None, the 
        existing settings on the service for that functionality are preserved.

        :param Metrics hour_metrics:
            The hour metrics settings provide a summary of request 
            statistics grouped by API in hourly aggregates for files.
        :param Metrics minute_metrics:
            The minute metrics settings provide request statistics 
            for each minute for files.
        :param cors:
            You can include up to five CorsRule elements in the 
            list. If an empty list is specified, all CORS rules will be deleted, 
            and CORS will be disabled for the service.
        :type cors: list(:class:`~azure.storage.common.models.CorsRule`)
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path()
        request.query = {
            'restype': 'service',
            'comp': 'properties',
            'timeout': _int_to_str(timeout),
        }
        request.body = _get_request_body(
            _convert_service_properties_to_xml(None, hour_metrics, minute_metrics, cors))

        self._perform_request(request)

    def get_file_service_properties(self, timeout=None):
        '''
        Gets the properties of a storage account's File service, including
        Azure Storage Analytics.

        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: The file service properties.
        :rtype:
            :class:`~azure.storage.common.models.ServiceProperties`
        '''
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path()
        request.query = {
            'restype': 'service',
            'comp': 'properties',
            'timeout': _int_to_str(timeout),
        }

        return self._perform_request(request, _convert_xml_to_service_properties)

    def list_shares(self, prefix=None, marker=None, num_results=None,
                    include_metadata=False, timeout=None, include_snapshots=False):
        '''
        Returns a generator to list the shares under the specified account.
        The generator will lazily follow the continuation tokens returned by
        the service and stop when all shares have been returned or num_results 
        is reached.

        If num_results is specified and the account has more than that number of 
        shares, the generator will have a populated next_marker field once it 
        finishes. This marker can be used to create a new generator if more 
        results are desired.

        :param str prefix:
            Filters the results to return only shares whose names
            begin with the specified prefix.
        :param int num_results:
            Specifies the maximum number of shares to return.
        :param bool include_metadata:
            Specifies that share metadata be returned in the response.
        :param str marker:
            An opaque continuation token. This value can be retrieved from the 
            next_marker field of a previous generator object if num_results was 
            specified and that generator has finished enumerating results. If 
            specified, this generator will begin returning results from the point 
            where the previous generator stopped.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param bool include_snapshots:
            Specifies that share snapshots be returned in the response.
        '''
        include = 'snapshots' if include_snapshots else None
        if include_metadata:
            if include is not None:
                include = include + ',metadata'
            else:
                include = 'metadata'
        operation_context = _OperationContext(location_lock=True)
        kwargs = {'prefix': prefix, 'marker': marker, 'max_results': num_results,
                  'include': include, 'timeout': timeout, '_context': operation_context}
        resp = self._list_shares(**kwargs)

        return ListGenerator(resp, self._list_shares, (), kwargs)

    def _list_shares(self, prefix=None, marker=None, max_results=None,
                     include=None, timeout=None, _context=None):
        '''
        Returns a list of the shares under the specified account.

        :param str prefix:
            Filters the results to return only shares whose names
            begin with the specified prefix.
        :param str marker:
            A string value that identifies the portion of the list
            to be returned with the next list operation. The operation returns
            a next_marker value within the response body if the list returned was
            not complete. The marker value may then be used in a subsequent
            call to request the next set of list items. The marker value is
            opaque to the client.
        :param int max_results:
            Specifies the maximum number of shares to return. A single list
            request may return up to 1000 shares and potentially a continuation
            token which should be followed to get additional resutls.
        :param string include:
            Include this parameter to specify that either the share's
            metadata, snapshots or both be returned as part of the response body. set this
            parameter to string 'metadata' to get share's metadata. set this parameter to 'snapshots'
            to get all the share snapshots. for both use 'snapshots,metadata'.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path()
        request.query = {
            'comp': 'list',
            'prefix': _to_str(prefix),
            'marker': _to_str(marker),
            'maxresults': _int_to_str(max_results),
            'include': _to_str(include),
            'timeout': _int_to_str(timeout),
        }

        return self._perform_request(request, _convert_xml_to_shares, operation_context=_context)

    def create_share(self, share_name, metadata=None, quota=None,
                     fail_on_exist=False, timeout=None):
        '''
        Creates a new share under the specified account. If the share
        with the same name already exists, the operation fails on the
        service. By default, the exception is swallowed by the client.
        To expose the exception, specify True for fail_on_exists.

        :param str share_name:
            Name of share to create.
        :param metadata:
            A dict with name_value pairs to associate with the
            share as metadata. Example:{'Category':'test'}
        :type metadata: dict(str, str)
        :param int quota:
            Specifies the maximum size of the share, in gigabytes. Must be 
            greater than 0, and less than or equal to 5TB (5120).
        :param bool fail_on_exist:
            Specify whether to throw an exception when the share exists.
            False by default.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: True if share is created, False if share already exists.
        :rtype: bool
        '''
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-share-quota': _int_to_str(quota)
        }
        _add_metadata_headers(metadata, request)

        if not fail_on_exist:
            try:
                self._perform_request(request, expected_errors=[_SHARE_ALREADY_EXISTS_ERROR_CODE])
                return True
            except AzureHttpError as ex:
                _dont_fail_on_exist(ex)
                return False
        else:
            self._perform_request(request)
            return True

    def snapshot_share(self, share_name, metadata=None, quota=None, timeout=None):
        '''
        Creates a snapshot of an existing share under the specified account.

        :param str share_name:
            The name of the share to create a snapshot of.
        :param metadata:
            A dict with name_value pairs to associate with the
            share as metadata. Example:{'Category':'test'}
        :type metadata: a dict of str to str:
        :param int quota:
            Specifies the maximum size of the share, in gigabytes. Must be
            greater than 0, and less than or equal to 5TB (5120).
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: snapshot properties
        :rtype: azure.storage.file.models.Share
        '''
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'comp': 'snapshot',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-share-quota': _int_to_str(quota)
        }
        _add_metadata_headers(metadata, request)

        return self._perform_request(request, _parse_snapshot_share, [share_name])

    def get_share_properties(self, share_name, timeout=None, snapshot=None):
        '''
        Returns all user-defined metadata and system properties for the
        specified share. The data returned does not include the shares's
        list of files or directories.

        :param str share_name:
            Name of existing share.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return: A Share that exposes properties and metadata.
        :rtype: :class:`~azure.storage.file.models.Share`
        '''
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot)
        }

        return self._perform_request(request, _parse_share, [share_name])

    def set_share_properties(self, share_name, quota, timeout=None):
        '''
        Sets service-defined properties for the specified share.

        :param str share_name:
            Name of existing share.
        :param int quota:
            Specifies the maximum size of the share, in gigabytes. Must be 
            greater than 0, and less than or equal to 5 TB (5120 GB).
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('quota', quota)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'comp': 'properties',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-share-quota': _int_to_str(quota)
        }

        self._perform_request(request)

    def get_share_metadata(self, share_name, timeout=None, snapshot=None):
        '''
        Returns all user-defined metadata for the specified share.

        :param str share_name:
            Name of existing share.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return:
            A dictionary representing the share metadata name, value pairs.
        :rtype: dict(str, str)
        '''
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'comp': 'metadata',
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot),
        }

        return self._perform_request(request, _parse_metadata)

    def set_share_metadata(self, share_name, metadata=None, timeout=None):
        '''
        Sets one or more user-defined name-value pairs for the specified
        share. Each call to this operation replaces all existing metadata
        attached to the share. To remove all metadata from the share,
        call this operation with no metadata dict.

        :param str share_name:
            Name of existing share.
        :param metadata:
            A dict containing name-value pairs to associate with the share as 
            metadata. Example: {'category':'test'}
        :type metadata: dict(str, str)
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'comp': 'metadata',
            'timeout': _int_to_str(timeout),
        }
        _add_metadata_headers(metadata, request)

        self._perform_request(request)

    def get_share_acl(self, share_name, timeout=None):
        '''
        Gets the permissions for the specified share.

        :param str share_name:
            Name of existing share.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: A dictionary of access policies associated with the share.
        :rtype: dict(str, :class:`~azure.storage.common.models.AccessPolicy`)
        '''
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'comp': 'acl',
            'timeout': _int_to_str(timeout),
        }

        return self._perform_request(request, _convert_xml_to_signed_identifiers)

    def set_share_acl(self, share_name, signed_identifiers=None, timeout=None):
        '''
        Sets the permissions for the specified share or stored access 
        policies that may be used with Shared Access Signatures.

        :param str share_name:
            Name of existing share.
        :param signed_identifiers:
            A dictionary of access policies to associate with the share. The 
            dictionary may contain up to 5 elements. An empty dictionary 
            will clear the access policies set on the service. 
        :type signed_identifiers: dict(str, :class:`~azure.storage.common.models.AccessPolicy`)
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_access_policies(signed_identifiers)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'comp': 'acl',
            'timeout': _int_to_str(timeout),
        }
        request.body = _get_request_body(
            _convert_signed_identifiers_to_xml(signed_identifiers))

        self._perform_request(request)

    def get_share_stats(self, share_name, timeout=None):
        '''
        Gets the approximate size of the data stored on the share,
        rounded up to the nearest gigabyte.
        
        Note that this value may not include all recently created
        or recently re-sized files.

        :param str share_name:
            Name of existing share.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: the approximate size of the data stored on the share.
        :rtype: int
        '''
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'comp': 'stats',
            'timeout': _int_to_str(timeout),
        }

        usage = self._perform_request(request, _convert_xml_to_share_stats)
        return int(math.ceil(float(usage) / _GB))

    def get_share_stats_in_bytes(self, share_name, timeout=None):
        """
        Gets the approximate size of the data stored on the share in bytes.

        Note that this value may not include all recently created
        or recently re-sized files.

        :param str share_name:
            Name of existing share.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: the approximate size of the data stored on the share.
        :rtype: int
        """
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.query = {
            'restype': 'share',
            'comp': 'stats',
            'timeout': _int_to_str(timeout),
        }

        return self._perform_request(request, _convert_xml_to_share_stats)

    def delete_share(self, share_name, fail_not_exist=False, timeout=None, snapshot=None, delete_snapshots=None):
        '''
        Marks the specified share for deletion. If the share
        does not exist, the operation fails on the service. By 
        default, the exception is swallowed by the client.
        To expose the exception, specify True for fail_not_exist.

        :param str share_name:
            Name of share to delete.
        :param bool fail_not_exist:
            Specify whether to throw an exception when the share doesn't
            exist. False by default.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
            Specify this argument to delete a specific snapshot only.
            delete_snapshots must be None if this is specified.
        :param ~azure.storage.file.models.DeleteSnapshot delete_snapshots:
            To delete a share that has snapshots, this must be specified as DeleteSnapshot.Include.
        :return: True if share is deleted, False share doesn't exist.
        :rtype: bool
        '''
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'DELETE'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name)
        request.headers = {
            'x-ms-delete-snapshots': _to_str(delete_snapshots)
        }
        request.query = {
            'restype': 'share',
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot),
        }

        if not fail_not_exist:
            try:
                self._perform_request(request, expected_errors=[_SHARE_NOT_FOUND_ERROR_CODE])
                return True
            except AzureHttpError as ex:
                _dont_fail_not_exist(ex)
                return False
        else:
            self._perform_request(request)
            return True

    def create_directory(self, share_name, directory_name, metadata=None,
                         fail_on_exist=False, timeout=None):
        '''
        Creates a new directory under the specified share or parent directory. 
        If the directory with the same name already exists, the operation fails
        on the service. By default, the exception is swallowed by the client.
        To expose the exception, specify True for fail_on_exists.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            Name of directory to create, including the path to the parent 
            directory.
        :param metadata:
            A dict with name_value pairs to associate with the
            share as metadata. Example:{'Category':'test'}
        :type metadata: dict(str, str):
        :param bool fail_on_exist:
            specify whether to throw an exception when the directory exists.
            False by default.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: True if directory is created, False if directory already exists.
        :rtype: bool
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('directory_name', directory_name)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name)
        request.query = {
            'restype': 'directory',
            'timeout': _int_to_str(timeout),
        }
        _add_metadata_headers(metadata, request)

        if not fail_on_exist:
            try:
                self._perform_request(request, expected_errors=_RESOURCE_ALREADY_EXISTS_ERROR_CODE)
                return True
            except AzureHttpError as ex:
                _dont_fail_on_exist(ex)
                return False
        else:
            self._perform_request(request)
            return True

    def delete_directory(self, share_name, directory_name,
                         fail_not_exist=False, timeout=None):
        '''
        Deletes the specified empty directory. Note that the directory must
        be empty before it can be deleted. Attempting to delete directories 
        that are not empty will fail.

        If the directory does not exist, the operation fails on the
        service. By default, the exception is swallowed by the client.
        To expose the exception, specify True for fail_not_exist.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            Name of directory to delete, including the path to the parent 
            directory.
        :param bool fail_not_exist:
            Specify whether to throw an exception when the directory doesn't
            exist.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: True if directory is deleted, False otherwise.
        :rtype: bool
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('directory_name', directory_name)
        request = HTTPRequest()
        request.method = 'DELETE'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name)
        request.query = {
            'restype': 'directory',
            'timeout': _int_to_str(timeout),
        }

        if not fail_not_exist:
            try:
                self._perform_request(request, expected_errors=[_RESOURCE_NOT_FOUND_ERROR_CODE])
                return True
            except AzureHttpError as ex:
                _dont_fail_not_exist(ex)
                return False
        else:
            self._perform_request(request)
            return True

    def get_directory_properties(self, share_name, directory_name, timeout=None, snapshot=None):
        '''
        Returns all user-defined metadata and system properties for the
        specified directory. The data returned does not include the directory's
        list of files.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
           The path to an existing directory.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: properties for the specified directory within a directory object.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :rtype: :class:`~azure.storage.file.models.Directory`
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('directory_name', directory_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name)
        request.query = {
            'restype': 'directory',
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot)
        }

        return self._perform_request(request, _parse_directory, [directory_name])

    def get_directory_metadata(self, share_name, directory_name, timeout=None, snapshot=None):
        '''
        Returns all user-defined metadata for the specified directory.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return:
            A dictionary representing the directory metadata name, value pairs.
        :rtype: dict(str, str)
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('directory_name', directory_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name)
        request.query = {
            'restype': 'directory',
            'comp': 'metadata',
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot)
        }

        return self._perform_request(request, _parse_metadata)

    def set_directory_metadata(self, share_name, directory_name, metadata=None, timeout=None):
        '''
        Sets one or more user-defined name-value pairs for the specified
        directory. Each call to this operation replaces all existing metadata
        attached to the directory. To remove all metadata from the directory,
        call this operation with no metadata dict.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param metadata:
            A dict containing name-value pairs to associate with the directory
            as metadata. Example: {'category':'test'}
        :type metadata: dict(str, str).
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('directory_name', directory_name)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name)
        request.query = {
            'restype': 'directory',
            'comp': 'metadata',
            'timeout': _int_to_str(timeout),
        }
        _add_metadata_headers(metadata, request)

        self._perform_request(request)

    def list_directories_and_files(self, share_name, directory_name=None,
                                   num_results=None, marker=None, timeout=None,
                                   prefix=None, snapshot=None):

        '''
        Returns a generator to list the directories and files under the specified share.
        The generator will lazily follow the continuation tokens returned by
        the service and stop when all directories and files have been returned or
        num_results is reached.

        If num_results is specified and the share has more than that number of 
        files and directories, the generator will have a populated next_marker 
        field once it finishes. This marker can be used to create a new generator 
        if more results are desired.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param int num_results:
            Specifies the maximum number of files to return,
            including all directory elements. If the request does not specify
            num_results or specifies a value greater than 5,000, the server will
            return up to 5,000 items. Setting num_results to a value less than
            or equal to zero results in error response code 400 (Bad Request).
        :param str marker:
            An opaque continuation token. This value can be retrieved from the 
            next_marker field of a previous generator object if num_results was 
            specified and that generator has finished enumerating results. If 
            specified, this generator will begin returning results from the point 
            where the previous generator stopped.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str prefix:
            List only the files and/or directories with the given prefix.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        '''
        operation_context = _OperationContext(location_lock=True)
        args = (share_name, directory_name)
        kwargs = {'marker': marker, 'max_results': num_results, 'timeout': timeout,
                  '_context': operation_context, 'prefix': prefix, 'snapshot': snapshot}

        resp = self._list_directories_and_files(*args, **kwargs)

        return ListGenerator(resp, self._list_directories_and_files, args, kwargs)

    def _list_directories_and_files(self, share_name, directory_name=None,
                                    marker=None, max_results=None, timeout=None,
                                    prefix=None, _context=None, snapshot=None):
        '''
        Returns a list of the directories and files under the specified share.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str marker:
            A string value that identifies the portion of the list
            to be returned with the next list operation. The operation returns
            a next_marker value within the response body if the list returned was
            not complete. The marker value may then be used in a subsequent
            call to request the next set of list items. The marker value is
            opaque to the client.
        :param int max_results:
            Specifies the maximum number of files to return,
            including all directory elements. If the request does not specify
            max_results or specifies a value greater than 5,000, the server will
            return up to 5,000 items. Setting max_results to a value less than
            or equal to zero results in error response code 400 (Bad Request).
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str prefix:
            List only the files and/or directories with the given prefix.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        '''
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name)
        request.query = {
            'restype': 'directory',
            'comp': 'list',
            'prefix': _to_str(prefix),
            'marker': _to_str(marker),
            'maxresults': _int_to_str(max_results),
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot)
        }

        return self._perform_request(request, _convert_xml_to_directories_and_files,
                                     operation_context=_context)

    def list_handles(self, share_name, directory_name=None, file_name=None, recursive=None,
                     max_results=None, marker=None, snapshot=None, timeout=None):

        """
        Returns a generator to list opened handles on a directory or a file under the specified share.
        The generator will lazily follow the continuation tokens returned by
        the service and stop when all handles have been returned or
        num_results is reached.

        If num_results is specified and the share has more than that number of
        files and directories, the generator will have a populated next_marker
        field once it finishes. This marker can be used to create a new generator
        if more results are desired.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param bool recursive:
            Boolean that specifies if operation should apply to the directory specified in the URI,
            its files, its subdirectories and their files.
        :param int max_results:
            Specifies the maximum number of handles taken on files and/or directories to return.
            If the request does not specify max_results or specifies a value greater than 5,000,
            the server will return up to 5,000 items.
            Setting max_results to a value less than or equal to zero results in error response code 400 (Bad Request).
        :param str marker:
            An opaque continuation token. This value can be retrieved from the
            next_marker field of a previous generator object if max_results was
            specified and that generator has finished enumerating results. If
            specified, this generator will begin returning results from the point
            where the previous generator stopped.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        """
        operation_context = _OperationContext(location_lock=True)
        args = (share_name, directory_name, file_name)
        kwargs = {'marker': marker, 'max_results': max_results, 'timeout': timeout, 'recursive': recursive,
                  '_context': operation_context, 'snapshot': snapshot}

        resp = self._list_handles(*args, **kwargs)

        return ListGenerator(resp, self._list_handles, args, kwargs)

    def _list_handles(self, share_name, directory_name=None, file_name=None, recursive=None,
                      marker=None, max_results=None, timeout=None, _context=None, snapshot=None):
        """
        Returns a list of the directories and files under the specified share.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param bool recursive:
            Boolean that specifies if operation should apply to the directory specified in the URI,
            its files, its subdirectories and their files.
        :param str marker:
            An opaque continuation token. This value can be retrieved from the
            next_marker field of a previous generator object if max_results was
            specified and that generator has finished enumerating results. If
            specified, this generator will begin returning results from the point
            where the previous generator stopped.
        :param int max_results:
            Specifies the maximum number of handles to return,
            including all directory elements. If the request does not specify
            max_results or specifies a value greater than 5,000, the server will
            return up to 5,000 items. Setting max_results to a value less than
            or equal to zero results in error response code 400 (Bad Request).
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        """
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'listhandles',
            'marker': _to_str(marker),
            'maxresults': _int_to_str(max_results),
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot)
        }
        request.headers = {
            'x-ms-recursive': _to_str(recursive)
        }

        return self._perform_request(request, _convert_xml_to_handles,
                                     operation_context=_context)

    def close_handles(self, share_name, directory_name=None, file_name=None, recursive=None,
                      handle_id=None, marker=None, snapshot=None, timeout=None):

        """
        Returns a generator to close opened handles on a directory or a file under the specified share.
        The generator will lazily follow the continuation tokens returned by
        the service and stop when all handles have been closed.
        The yielded values represent the number of handles that were closed in each transaction.


        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param bool recursive:
            Boolean that specifies if operation should apply to the directory specified in the URI,
            its files, its subdirectories and their files.
        :param str handle_id:
            Required. Specifies handle ID opened on the file or directory to be closed.
            Astrix (‘*’) is a wildcard that specifies all handles.
        :param str marker:
            An opaque continuation token. This value can be retrieved from the
            next_marker field of a previous generator object if it has not finished closing handles. If
            specified, this generator will begin closing handles from the point
            where the previous generator stopped.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        """
        operation_context = _OperationContext(location_lock=True)
        args = (share_name, directory_name, file_name)
        kwargs = {'marker': marker, 'handle_id': handle_id, 'timeout': timeout, 'recursive': recursive,
                  '_context': operation_context, 'snapshot': snapshot}

        resp = self._close_handles(*args, **kwargs)

        return ListGenerator(resp, self._close_handles, args, kwargs)

    def _close_handles(self, share_name, directory_name=None, file_name=None, recursive=None, handle_id=None,
                       marker=None, timeout=None, _context=None, snapshot=None):
        """
        Returns the number of handles that got closed.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param bool recursive:
            Boolean that specifies if operation should apply to the directory specified in the URI,
            its files, its subdirectories and their files.
        :param str handle_id:
            Required. Specifies handle ID opened on the file or directory to be closed.
            Astrix (‘*’) is a wildcard that specifies all handles.
        :param str marker:
            Specifies the maximum number of handles taken on files and/or directories to return.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        """
        _validate_not_none('share_name', share_name)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'forceclosehandles',
            'marker': _to_str(marker),
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot)
        }
        request.headers = {
            'x-ms-recursive': _to_str(recursive),
            'x-ms-handle-id': _to_str(handle_id),
        }

        return self._perform_request(request, _parse_close_handle_response, operation_context=_context)

    def get_file_properties(self, share_name, directory_name, file_name, timeout=None, snapshot=None):
        '''
        Returns all user-defined metadata, standard HTTP properties, and
        system properties for the file. Returns an instance of :class:`~azure.storage.file.models.File` with
        :class:`~azure.storage.file.models.FileProperties` and a metadata dict.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return: a file object including properties and metadata.
        :rtype: :class:`~azure.storage.file.models.File`
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        request = HTTPRequest()
        request.method = 'HEAD'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {'timeout': _int_to_str(timeout), 'sharesnapshot': _to_str(snapshot)}

        return self._perform_request(request, _parse_file, [file_name])

    def exists(self, share_name, directory_name=None, file_name=None, timeout=None, snapshot=None):
        '''
        Returns a boolean indicating whether the share exists if only share name is
        given. If directory_name is specificed a boolean will be returned indicating
        if the directory exists. If file_name is specified as well, a boolean will be
        returned indicating if the file exists.

        :param str share_name:
            Name of a share.
        :param str directory_name:
            The path to a directory.
        :param str file_name:
            Name of a file.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return: A boolean indicating whether the resource exists.
        :rtype: bool
        '''
        _validate_not_none('share_name', share_name)
        try:
            request = HTTPRequest()
            request.method = 'HEAD' if file_name is not None else 'GET'
            request.host_locations = self._get_host_locations()
            request.path = _get_path(share_name, directory_name, file_name)

            if file_name is not None:
                restype = None
                expected_errors = [_RESOURCE_NOT_FOUND_ERROR_CODE, _PARENT_NOT_FOUND_ERROR_CODE]
            elif directory_name is not None:
                restype = 'directory'
                expected_errors = [_RESOURCE_NOT_FOUND_ERROR_CODE, _SHARE_NOT_FOUND_ERROR_CODE,
                                   _PARENT_NOT_FOUND_ERROR_CODE]
            else:
                restype = 'share'
                expected_errors = [_SHARE_NOT_FOUND_ERROR_CODE]

            request.query = {
                'restype': restype,
                'timeout': _int_to_str(timeout),
                'sharesnapshot': _to_str(snapshot)
            }
            self._perform_request(request, expected_errors=expected_errors)
            return True
        except AzureHttpError as ex:
            _dont_fail_not_exist(ex)
            return False

    def resize_file(self, share_name, directory_name,
                    file_name, content_length, timeout=None):
        '''
        Resizes a file to the specified size. If the specified byte
        value is less than the current size of the file, then all
        ranges above the specified byte value are cleared.
        
        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param int content_length:
            The length to resize the file to.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('content_length', content_length)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'properties',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-content-length': _to_str(content_length)
        }

        self._perform_request(request)

    def set_file_properties(self, share_name, directory_name, file_name,
                            content_settings, timeout=None):
        '''
        Sets system properties on the file. If one property is set for the
        content_settings, all properties will be overriden.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param ~azure.storage.file.models.ContentSettings content_settings:
            ContentSettings object used to set the file properties.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('content_settings', content_settings)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'properties',
            'timeout': _int_to_str(timeout),
        }
        request.headers = content_settings._to_headers()

        self._perform_request(request)

    def get_file_metadata(self, share_name, directory_name, file_name, timeout=None, snapshot=None):
        '''
        Returns all user-defined metadata for the specified file.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return:
            A dictionary representing the file metadata name, value pairs.
        :rtype: dict(str, str)
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'metadata',
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot),
        }

        return self._perform_request(request, _parse_metadata)

    def set_file_metadata(self, share_name, directory_name,
                          file_name, metadata=None, timeout=None):
        '''
        Sets user-defined metadata for the specified file as one or more
        name-value pairs.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param metadata:
            Dict containing name and value pairs. Each call to this operation
            replaces all existing metadata attached to the file. To remove all
            metadata from the file, call this operation with no metadata headers.
        :type metadata: dict(str, str)
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'metadata',
            'timeout': _int_to_str(timeout),
        }
        _add_metadata_headers(metadata, request)

        self._perform_request(request)

    def copy_file(self, share_name, directory_name, file_name, copy_source,
                  metadata=None, timeout=None):
        '''
        Copies a file asynchronously. This operation returns a copy operation 
        properties object, including a copy ID you can use to check or abort the 
        copy operation. The File service copies files on a best-effort basis.

        If the destination file exists, it will be overwritten. The destination 
        file cannot be modified while the copy operation is in progress.

        :param str share_name:
            Name of the destination share. The share must exist.
        :param str directory_name:
            Name of the destination directory. The directory must exist.
        :param str file_name:
            Name of the destination file. If the destination file exists, it will 
            be overwritten. Otherwise, it will be created.
        :param str copy_source:
            A URL of up to 2 KB in length that specifies an Azure file or blob. 
            The value should be URL-encoded as it would appear in a request URI. 
            If the source is in another account, the source must either be public 
            or must be authenticated via a shared access signature. If the source 
            is public, no authentication is required.
            Examples:
            https://myaccount.file.core.windows.net/myshare/mydir/myfile
            https://otheraccount.file.core.windows.net/myshare/mydir/myfile?sastoken
        :param metadata:
            Name-value pairs associated with the file as metadata. If no name-value 
            pairs are specified, the operation will copy the metadata from the 
            source blob or file to the destination file. If one or more name-value 
            pairs are specified, the destination file is created with the specified 
            metadata, and the metadata is not copied from the source blob or file. 
        :type metadata: dict(str, str).
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :return: Copy operation properties such as status, source, and ID.
        :rtype: :class:`~azure.storage.file.models.CopyProperties`
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('copy_source', copy_source)

        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {'timeout': _int_to_str(timeout)}
        request.headers = {
            'x-ms-copy-source': _to_str(copy_source),
        }
        _add_metadata_headers(metadata, request)

        return self._perform_request(request, _parse_properties, [FileProperties]).copy

    def abort_copy_file(self, share_name, directory_name, file_name, copy_id, timeout=None):
        '''
         Aborts a pending copy_file operation, and leaves a destination file
         with zero length and full metadata.

        :param str share_name:
             Name of destination share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
             Name of destination file.
        :param str copy_id:
            Copy identifier provided in the copy.id of the original
            copy_file operation.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('copy_id', copy_id)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'copy',
            'copyid': _to_str(copy_id),
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-copy-action': 'abort',
        }

        self._perform_request(request)

    def delete_file(self, share_name, directory_name, file_name, timeout=None):
        '''
        Marks the specified file for deletion. The file is later
        deleted during garbage collection.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        request = HTTPRequest()
        request.method = 'DELETE'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {'timeout': _int_to_str(timeout)}

        self._perform_request(request)

    def create_file(self, share_name, directory_name, file_name,
                    content_length, content_settings=None, metadata=None,
                    timeout=None):
        '''
        Creates a new file.

        See create_file_from_* for high level functions that handle the
        creation and upload of large files with automatic chunking and
        progress notifications.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of file to create or update.
        :param int content_length:
            Length of the file in bytes.
        :param ~azure.storage.file.models.ContentSettings content_settings:
            ContentSettings object used to set file properties.
        :param metadata:
            Name-value pairs associated with the file as metadata.
        :type metadata: dict(str, str)
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('content_length', content_length)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {'timeout': _int_to_str(timeout)}
        request.headers = {
            'x-ms-content-length': _to_str(content_length),
            'x-ms-type': 'file'
        }
        _add_metadata_headers(metadata, request)
        if content_settings is not None:
            request.headers.update(content_settings._to_headers())

        self._perform_request(request)

    def create_file_from_path(self, share_name, directory_name, file_name,
                              local_file_path, content_settings=None,
                              metadata=None, validate_content=False, progress_callback=None,
                              max_connections=2, timeout=None):
        '''
        Creates a new azure file from a local file path, or updates the content of an
        existing file, with automatic chunking and progress notifications.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of file to create or update.
        :param str local_file_path:
            Path of the local file to upload as the file content.
        :param ~azure.storage.file.models.ContentSettings content_settings:
            ContentSettings object used for setting file properties.
        :param metadata:
            Name-value pairs associated with the file as metadata.
        :type metadata: dict(str, str)
        :param bool validate_content:
            If true, calculates an MD5 hash for each range of the file. The storage 
            service checks the hash of the content that has arrived with the hash 
            that was sent. This is primarily valuable for detecting bitflips on 
            the wire if using http instead of https as https (the default) will 
            already validate. Note that this MD5 hash is not stored with the 
            file.
        :param progress_callback:
            Callback for progress with signature function(current, total) where
            current is the number of bytes transfered so far and total is the
            size of the file, or None if the total size is unknown.
        :type progress_callback: func(current, total)
        :param int max_connections:
            Maximum number of parallel connections to use.
        :param int timeout:
            The timeout parameter is expressed in seconds. This method may make 
            multiple calls to the Azure service and the timeout will apply to 
            each call individually.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('local_file_path', local_file_path)

        count = path.getsize(local_file_path)
        with open(local_file_path, 'rb') as stream:
            self.create_file_from_stream(
                share_name, directory_name, file_name, stream,
                count, content_settings, metadata, validate_content, progress_callback,
                max_connections, timeout)

    def create_file_from_text(self, share_name, directory_name, file_name,
                              text, encoding='utf-8', content_settings=None,
                              metadata=None, validate_content=False, timeout=None):
        '''
        Creates a new file from str/unicode, or updates the content of an
        existing file, with automatic chunking and progress notifications.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of file to create or update.
        :param str text:
            Text to upload to the file.
        :param str encoding:
            Python encoding to use to convert the text to bytes.
        :param ~azure.storage.file.models.ContentSettings content_settings:
            ContentSettings object used to set file properties.
        :param metadata:
            Name-value pairs associated with the file as metadata.
        :type metadata: dict(str, str)
        :param bool validate_content:
            If true, calculates an MD5 hash for each range of the file. The storage 
            service checks the hash of the content that has arrived with the hash 
            that was sent. This is primarily valuable for detecting bitflips on 
            the wire if using http instead of https as https (the default) will 
            already validate. Note that this MD5 hash is not stored with the 
            file.
        :param int timeout:
            The timeout parameter is expressed in seconds. This method may make 
            multiple calls to the Azure service and the timeout will apply to 
            each call individually.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('text', text)

        if not isinstance(text, bytes):
            _validate_not_none('encoding', encoding)
            text = text.encode(encoding)

        self.create_file_from_bytes(
            share_name, directory_name, file_name, text, count=len(text),
            content_settings=content_settings, metadata=metadata,
            validate_content=validate_content, timeout=timeout)

    def create_file_from_bytes(
            self, share_name, directory_name, file_name, file,
            index=0, count=None, content_settings=None, metadata=None,
            validate_content=False, progress_callback=None, max_connections=2,
            timeout=None):
        '''
        Creates a new file from an array of bytes, or updates the content
        of an existing file, with automatic chunking and progress
        notifications.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of file to create or update.
        :param str file:
            Content of file as an array of bytes.
        :param int index:
            Start index in the array of bytes.
        :param int count:
            Number of bytes to upload. Set to None or negative value to upload
            all bytes starting from index.
        :param ~azure.storage.file.models.ContentSettings content_settings:
            ContentSettings object used to set file properties.
        :param metadata:
            Name-value pairs associated with the file as metadata.
        :type metadata: dict(str, str)
        :param bool validate_content:
            If true, calculates an MD5 hash for each range of the file. The storage 
            service checks the hash of the content that has arrived with the hash 
            that was sent. This is primarily valuable for detecting bitflips on 
            the wire if using http instead of https as https (the default) will 
            already validate. Note that this MD5 hash is not stored with the 
            file.
        :param progress_callback:
            Callback for progress with signature function(current, total) where
            current is the number of bytes transfered so far and total is the
            size of the file, or None if the total size is unknown.
        :type progress_callback: func(current, total)
        :param int max_connections:
            Maximum number of parallel connections to use.
        :param int timeout:
            The timeout parameter is expressed in seconds. This method may make 
            multiple calls to the Azure service and the timeout will apply to 
            each call individually.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('file', file)
        _validate_type_bytes('file', file)

        if index < 0:
            raise TypeError(_ERROR_VALUE_NEGATIVE.format('index'))

        if count is None or count < 0:
            count = len(file) - index

        stream = BytesIO(file)
        stream.seek(index)

        self.create_file_from_stream(
            share_name, directory_name, file_name, stream, count,
            content_settings, metadata, validate_content, progress_callback,
            max_connections, timeout)

    def create_file_from_stream(
            self, share_name, directory_name, file_name, stream, count,
            content_settings=None, metadata=None, validate_content=False,
            progress_callback=None, max_connections=2, timeout=None):
        '''
        Creates a new file from a file/stream, or updates the content of an
        existing file, with automatic chunking and progress notifications.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of file to create or update.
        :param io.IOBase stream:
            Opened file/stream to upload as the file content.
        :param int count:
            Number of bytes to read from the stream. This is required, a
            file cannot be created if the count is unknown.
        :param ~azure.storage.file.models.ContentSettings content_settings:
            ContentSettings object used to set file properties.
        :param metadata:
            Name-value pairs associated with the file as metadata.
        :type metadata: dict(str, str)
        :param bool validate_content:
            If true, calculates an MD5 hash for each range of the file. The storage 
            service checks the hash of the content that has arrived with the hash 
            that was sent. This is primarily valuable for detecting bitflips on 
            the wire if using http instead of https as https (the default) will 
            already validate. Note that this MD5 hash is not stored with the 
            file.
        :param progress_callback:
            Callback for progress with signature function(current, total) where
            current is the number of bytes transfered so far and total is the
            size of the file, or None if the total size is unknown.
        :type progress_callback: func(current, total)
        :param int max_connections:
            Maximum number of parallel connections to use. Note that parallel upload 
            requires the stream to be seekable.
        :param int timeout:
            The timeout parameter is expressed in seconds. This method may make 
            multiple calls to the Azure service and the timeout will apply to 
            each call individually.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('stream', stream)
        _validate_not_none('count', count)

        if count < 0:
            raise TypeError(_ERROR_VALUE_NEGATIVE.format('count'))

        self.create_file(
            share_name,
            directory_name,
            file_name,
            count,
            content_settings,
            metadata,
            timeout
        )

        _upload_file_chunks(
            self,
            share_name,
            directory_name,
            file_name,
            count,
            self.MAX_RANGE_SIZE,
            stream,
            max_connections,
            progress_callback,
            validate_content,
            timeout
        )

    def _get_file(self, share_name, directory_name, file_name,
                  start_range=None, end_range=None, validate_content=False,
                  timeout=None, _context=None, snapshot=None):
        '''
        Downloads a file's content, metadata, and properties. You can specify a
        range if you don't need to download the file in its entirety. If no range
        is specified, the full file will be downloaded.

        See get_file_to_* for high level functions that handle the download
        of large files with automatic chunking and progress notifications.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param int start_range:
            Start of byte range to use for downloading a section of the file.
            If no end_range is given, all bytes after the start_range will be downloaded.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int end_range:
            End of byte range to use for downloading a section of the file.
            If end_range is given, start_range must be provided.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param bool validate_content:
            When this is set to True and specified together with the Range header, 
            the service returns the MD5 hash for the range, as long as the range 
            is less than or equal to 4 MB in size.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return: A File with content, properties, and metadata.
        :rtype: :class:`~azure.storage.file.models.File`
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {'timeout': _int_to_str(timeout), 'sharesnapshot': _to_str(snapshot)}
        _validate_and_format_range_headers(
            request,
            start_range,
            end_range,
            start_range_required=False,
            end_range_required=False,
            check_content_md5=validate_content)

        return self._perform_request(request, _parse_file,
                                     [file_name, validate_content],
                                     operation_context=_context)

    def get_file_to_path(self, share_name, directory_name, file_name, file_path,
                         open_mode='wb', start_range=None, end_range=None,
                         validate_content=False, progress_callback=None,
                         max_connections=2, timeout=None, snapshot=None):
        '''
        Downloads a file to a file path, with automatic chunking and progress
        notifications. Returns an instance of File with properties and metadata.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param str file_path:
            Path of file to write to.
        :param str open_mode:
            Mode to use when opening the file. Note that specifying append only 
            open_mode prevents parallel download. So, max_connections must be set 
            to 1 if this open_mode is used.
        :param int start_range:
            Start of byte range to use for downloading a section of the file.
            If no end_range is given, all bytes after the start_range will be downloaded.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int end_range:
            End of byte range to use for downloading a section of the file.
            If end_range is given, start_range must be provided.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param bool validate_content:
            If set to true, validates an MD5 hash for each retrieved portion of 
            the file. This is primarily valuable for detecting bitflips on the wire 
            if using http instead of https as https (the default) will already 
            validate. Note that the service will only return transactional MD5s 
            for chunks 4MB or less so the first get request will be of size 
            self.MAX_CHUNK_GET_SIZE instead of self.MAX_SINGLE_GET_SIZE. If 
            self.MAX_CHUNK_GET_SIZE was set to greater than 4MB an error will be 
            thrown. As computing the MD5 takes processing time and more requests 
            will need to be done due to the reduced chunk size there may be some 
            increase in latency.
        :param progress_callback:
            Callback for progress with signature function(current, total) 
            where current is the number of bytes transfered so far, and total is 
            the size of the file if known.
        :type progress_callback: func(current, total)
        :param int max_connections:
            If set to 2 or greater, an initial get will be done for the first 
            self.MAX_SINGLE_GET_SIZE bytes of the file. If this is the entire file, 
            the method returns at this point. If it is not, it will download the 
            remaining data parallel using the number of threads equal to 
            max_connections. Each chunk will be of size self.MAX_CHUNK_GET_SIZE.
            If set to 1, a single large get request will be done. This is not 
            generally recommended but available if very few threads should be 
            used, network requests are very expensive, or a non-seekable stream 
            prevents parallel download. This may also be valuable if the file is 
            being concurrently modified to enforce atomicity or if many files are 
            expected to be empty as an extra request is required for empty files 
            if max_connections is greater than 1.
        :param int timeout:
            The timeout parameter is expressed in seconds. This method may make 
            multiple calls to the Azure service and the timeout will apply to 
            each call individually.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return: A File with properties and metadata.
        :rtype: :class:`~azure.storage.file.models.File`
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('file_path', file_path)
        _validate_not_none('open_mode', open_mode)

        if max_connections > 1 and 'a' in open_mode:
            raise ValueError(_ERROR_PARALLEL_NOT_SEEKABLE)

        with open(file_path, open_mode) as stream:
            file = self.get_file_to_stream(
                share_name, directory_name, file_name, stream,
                start_range, end_range, validate_content,
                progress_callback, max_connections, timeout, snapshot)

        return file

    def get_file_to_stream(
            self, share_name, directory_name, file_name, stream,
            start_range=None, end_range=None, validate_content=False,
            progress_callback=None, max_connections=2, timeout=None, snapshot=None):
        '''
        Downloads a file to a stream, with automatic chunking and progress
        notifications. Returns an instance of :class:`~azure.storage.file.models.File` with properties
        and metadata.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param io.IOBase stream:
            Opened file/stream to write to.
        :param int start_range:
            Start of byte range to use for downloading a section of the file.
            If no end_range is given, all bytes after the start_range will be downloaded.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int end_range:
            End of byte range to use for downloading a section of the file.
            If end_range is given, start_range must be provided.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param bool validate_content:
            If set to true, validates an MD5 hash for each retrieved portion of 
            the file. This is primarily valuable for detecting bitflips on the wire 
            if using http instead of https as https (the default) will already 
            validate. Note that the service will only return transactional MD5s 
            for chunks 4MB or less so the first get request will be of size 
            self.MAX_CHUNK_GET_SIZE instead of self.MAX_SINGLE_GET_SIZE. If 
            self.MAX_CHUNK_GET_SIZE was set to greater than 4MB an error will be 
            thrown. As computing the MD5 takes processing time and more requests 
            will need to be done due to the reduced chunk size there may be some 
            increase in latency.
        :param progress_callback:
            Callback for progress with signature function(current, total) 
            where current is the number of bytes transfered so far, and total is 
            the size of the file if known.
        :type progress_callback: func(current, total)
        :param int max_connections:
            If set to 2 or greater, an initial get will be done for the first 
            self.MAX_SINGLE_GET_SIZE bytes of the file. If this is the entire file, 
            the method returns at this point. If it is not, it will download the 
            remaining data parallel using the number of threads equal to 
            max_connections. Each chunk will be of size self.MAX_CHUNK_GET_SIZE.
            If set to 1, a single large get request will be done. This is not 
            generally recommended but available if very few threads should be 
            used, network requests are very expensive, or a non-seekable stream 
            prevents parallel download. This may also be valuable if the file is 
            being concurrently modified to enforce atomicity or if many files are 
            expected to be empty as an extra request is required for empty files 
            if max_connections is greater than 1.
        :param int timeout:
            The timeout parameter is expressed in seconds. This method may make 
            multiple calls to the Azure service and the timeout will apply to 
            each call individually.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return: A File with properties and metadata.
        :rtype: :class:`~azure.storage.file.models.File`
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('stream', stream)

        if end_range is not None:
            _validate_not_none("start_range", start_range)

        # the stream must be seekable if parallel download is required
        if max_connections > 1:
            if sys.version_info >= (3,) and not stream.seekable():
                raise ValueError(_ERROR_PARALLEL_NOT_SEEKABLE)
            else:
                try:
                    stream.seek(stream.tell())
                except (NotImplementedError, AttributeError):
                    raise ValueError(_ERROR_PARALLEL_NOT_SEEKABLE)

        # The service only provides transactional MD5s for chunks under 4MB.
        # If validate_content is on, get only self.MAX_CHUNK_GET_SIZE for the first
        # chunk so a transactional MD5 can be retrieved.
        first_get_size = self.MAX_SINGLE_GET_SIZE if not validate_content else self.MAX_CHUNK_GET_SIZE

        initial_request_start = start_range if start_range is not None else 0

        if end_range is not None and end_range - start_range < first_get_size:
            initial_request_end = end_range
        else:
            initial_request_end = initial_request_start + first_get_size - 1

        # Send a context object to make sure we always retry to the initial location
        operation_context = _OperationContext(location_lock=True)
        try:
            file = self._get_file(share_name,
                                  directory_name,
                                  file_name,
                                  start_range=initial_request_start,
                                  end_range=initial_request_end,
                                  validate_content=validate_content,
                                  timeout=timeout,
                                  _context=operation_context,
                                  snapshot=snapshot)

            # Parse the total file size and adjust the download size if ranges
            # were specified
            file_size = _parse_length_from_content_range(file.properties.content_range)
            if end_range is not None:
                # Use the end_range unless it is over the end of the file
                download_size = min(file_size, end_range - start_range + 1)
            elif start_range is not None:
                download_size = file_size - start_range
            else:
                download_size = file_size
        except AzureHttpError as ex:
            if start_range is None and ex.status_code == 416:
                # Get range will fail on an empty file. If the user did not
                # request a range, do a regular get request in order to get
                # any properties.
                file = self._get_file(share_name,
                                      directory_name,
                                      file_name,
                                      validate_content=validate_content,
                                      timeout=timeout,
                                      _context=operation_context,
                                      snapshot=snapshot)

                # Set the download size to empty
                download_size = 0
            else:
                raise ex

        # Mark the first progress chunk. If the file is small, this is the only call
        if progress_callback:
            progress_callback(file.properties.content_length, download_size)

        # Write the content to the user stream  
        # Clear file content since output has been written to user stream   
        if file.content is not None:
            stream.write(file.content)
            file.content = None

        # If the file is small, the download is complete at this point.
        # If file size is large, download the rest of the blob in chunks.
        if file.properties.content_length != download_size:
            # At this point we would like to lock on something like the etag so that
            # if the file is modified, we do not get a corrupted download. However,
            # this feature is not yet available on the file service.

            end_file = file_size
            if end_range is not None:
                # Use the end_range unless it is over the end of the file
                end_file = min(file_size, end_range + 1)

            _download_file_chunks(
                self,
                share_name,
                directory_name,
                file_name,
                download_size,
                self.MAX_CHUNK_GET_SIZE,
                first_get_size,
                initial_request_end + 1,  # start where the first download ended
                end_file,
                stream,
                max_connections,
                progress_callback,
                validate_content,
                timeout,
                operation_context,
                snapshot
            )

            # Set the content length to the download size instead of the size of 
            # the last range
            file.properties.content_length = download_size

            # Overwrite the content range to the user requested range
            file.properties.content_range = 'bytes {0}-{1}/{2}'.format(start_range, end_range, file_size)

            # Overwrite the content MD5 as it is the MD5 for the last range instead 
            # of the stored MD5
            # TODO: Set to the stored MD5 when the service returns this
            file.properties.content_md5 = None

        return file

    def get_file_to_bytes(self, share_name, directory_name, file_name,
                          start_range=None, end_range=None, validate_content=False,
                          progress_callback=None, max_connections=2, timeout=None, snapshot=None):
        '''
        Downloads a file as an array of bytes, with automatic chunking and
        progress notifications. Returns an instance of :class:`~azure.storage.file.models.File` with
        properties, metadata, and content.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param int start_range:
            Start of byte range to use for downloading a section of the file.
            If no end_range is given, all bytes after the start_range will be downloaded.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int end_range:
            End of byte range to use for downloading a section of the file.
            If end_range is given, start_range must be provided.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param bool validate_content:
            If set to true, validates an MD5 hash for each retrieved portion of 
            the file. This is primarily valuable for detecting bitflips on the wire 
            if using http instead of https as https (the default) will already 
            validate. Note that the service will only return transactional MD5s 
            for chunks 4MB or less so the first get request will be of size 
            self.MAX_CHUNK_GET_SIZE instead of self.MAX_SINGLE_GET_SIZE. If 
            self.MAX_CHUNK_GET_SIZE was set to greater than 4MB an error will be 
            thrown. As computing the MD5 takes processing time and more requests 
            will need to be done due to the reduced chunk size there may be some 
            increase in latency.
        :param progress_callback:
            Callback for progress with signature function(current, total) 
            where current is the number of bytes transfered so far, and total is 
            the size of the file if known.
        :type progress_callback: func(current, total)
        :param int max_connections:
            If set to 2 or greater, an initial get will be done for the first 
            self.MAX_SINGLE_GET_SIZE bytes of the file. If this is the entire file, 
            the method returns at this point. If it is not, it will download the 
            remaining data parallel using the number of threads equal to 
            max_connections. Each chunk will be of size self.MAX_CHUNK_GET_SIZE.
            If set to 1, a single large get request will be done. This is not 
            generally recommended but available if very few threads should be 
            used, network requests are very expensive, or a non-seekable stream 
            prevents parallel download. This may also be valuable if the file is 
            being concurrently modified to enforce atomicity or if many files are 
            expected to be empty as an extra request is required for empty files 
            if max_connections is greater than 1.
        :param int timeout:
            The timeout parameter is expressed in seconds. This method may make 
            multiple calls to the Azure service and the timeout will apply to 
            each call individually.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return: A File with properties, content, and metadata.
        :rtype: :class:`~azure.storage.file.models.File`
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)

        stream = BytesIO()
        file = self.get_file_to_stream(
            share_name,
            directory_name,
            file_name,
            stream,
            start_range,
            end_range,
            validate_content,
            progress_callback,
            max_connections,
            timeout,
            snapshot)

        file.content = stream.getvalue()
        return file

    def get_file_to_text(
            self, share_name, directory_name, file_name, encoding='utf-8',
            start_range=None, end_range=None, validate_content=False,
            progress_callback=None, max_connections=2, timeout=None, snapshot=None):
        '''
        Downloads a file as unicode text, with automatic chunking and progress
        notifications. Returns an instance of :class:`~azure.storage.file.models.File` with properties,
        metadata, and content.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param str encoding:
            Python encoding to use when decoding the file data.
        :param int start_range:
            Start of byte range to use for downloading a section of the file.
            If no end_range is given, all bytes after the start_range will be downloaded.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int end_range:
            End of byte range to use for downloading a section of the file.
            If end_range is given, start_range must be provided.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param bool validate_content:
            If set to true, validates an MD5 hash for each retrieved portion of 
            the file. This is primarily valuable for detecting bitflips on the wire 
            if using http instead of https as https (the default) will already 
            validate. Note that the service will only return transactional MD5s 
            for chunks 4MB or less so the first get request will be of size 
            self.MAX_CHUNK_GET_SIZE instead of self.MAX_SINGLE_GET_SIZE. If 
            self.MAX_CHUNK_GET_SIZE was set to greater than 4MB an error will be 
            thrown. As computing the MD5 takes processing time and more requests 
            will need to be done due to the reduced chunk size there may be some 
            increase in latency.
        :param progress_callback:
            Callback for progress with signature function(current, total) 
            where current is the number of bytes transfered so far, and total is 
            the size of the file if known.
        :type progress_callback: func(current, total)
        :param int max_connections:
            If set to 2 or greater, an initial get will be done for the first 
            self.MAX_SINGLE_GET_SIZE bytes of the file. If this is the entire file, 
            the method returns at this point. If it is not, it will download the 
            remaining data parallel using the number of threads equal to 
            max_connections. Each chunk will be of size self.MAX_CHUNK_GET_SIZE.
            If set to 1, a single large get request will be done. This is not 
            generally recommended but available if very few threads should be 
            used, network requests are very expensive, or a non-seekable stream 
            prevents parallel download. This may also be valuable if the file is 
            being concurrently modified to enforce atomicity or if many files are 
            expected to be empty as an extra request is required for empty files 
            if max_connections is greater than 1.
        :param int timeout:
            The timeout parameter is expressed in seconds. This method may make 
            multiple calls to the Azure service and the timeout will apply to 
            each call individually.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :return: A File with properties, content, and metadata.
        :rtype: :class:`~azure.storage.file.models.File`
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('encoding', encoding)

        file = self.get_file_to_bytes(
            share_name,
            directory_name,
            file_name,
            start_range,
            end_range,
            validate_content,
            progress_callback,
            max_connections,
            timeout,
            snapshot)

        file.content = file.content.decode(encoding)
        return file

    def update_range(self, share_name, directory_name, file_name, data,
                     start_range, end_range, validate_content=False, timeout=None):
        '''
        Writes the bytes specified by the request body into the specified range.
         
        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param bytes data:
            Content of the range.
        :param int start_range:
            Start of byte range to use for updating a section of the file.
            The range can be up to 4 MB in size.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int end_range:
            End of byte range to use for updating a section of the file.
            The range can be up to 4 MB in size.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param bool validate_content:
            If true, calculates an MD5 hash of the page content. The storage 
            service checks the hash of the content that has arrived
            with the hash that was sent. This is primarily valuable for detecting 
            bitflips on the wire if using http instead of https as https (the default) 
            will already validate. Note that this MD5 hash is not stored with the 
            file.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        _validate_not_none('data', data)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'range',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'x-ms-write': 'update',
        }
        _validate_and_format_range_headers(
            request, start_range, end_range)
        request.body = _get_data_bytes_only('data', data)

        if validate_content:
            computed_md5 = _get_content_md5(request.body)
            request.headers['Content-MD5'] = _to_str(computed_md5)

        self._perform_request(request)

    def clear_range(self, share_name, directory_name, file_name, start_range,
                    end_range, timeout=None):
        '''
        Clears the specified range and releases the space used in storage for 
        that range.
         
        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param int start_range:
            Start of byte range to use for clearing a section of the file.
            The range can be up to 4 MB in size.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int end_range:
            End of byte range to use for clearing a section of the file.
            The range can be up to 4 MB in size.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        request = HTTPRequest()
        request.method = 'PUT'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'range',
            'timeout': _int_to_str(timeout),
        }
        request.headers = {
            'Content-Length': '0',
            'x-ms-write': 'clear',
        }
        _validate_and_format_range_headers(
            request, start_range, end_range)

        self._perform_request(request)

    def list_ranges(self, share_name, directory_name, file_name,
                    start_range=None, end_range=None, timeout=None, snapshot=None):
        '''
        Retrieves the valid ranges for a file.

        :param str share_name:
            Name of existing share.
        :param str directory_name:
            The path to the directory.
        :param str file_name:
            Name of existing file.
        :param int start_range:
            Specifies the start offset of bytes over which to list ranges.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int end_range:
            Specifies the end offset of bytes over which to list ranges.
            The start_range and end_range params are inclusive.
            Ex: start_range=0, end_range=511 will download first 512 bytes of file.
        :param int timeout:
            The timeout parameter is expressed in seconds.
        :param str snapshot:
            A string that represents the snapshot version, if applicable.
        :returns: a list of valid ranges
        :rtype: a list of :class:`~azure.storage.file.models.FileRange`
        '''
        _validate_not_none('share_name', share_name)
        _validate_not_none('file_name', file_name)
        request = HTTPRequest()
        request.method = 'GET'
        request.host_locations = self._get_host_locations()
        request.path = _get_path(share_name, directory_name, file_name)
        request.query = {
            'comp': 'rangelist',
            'timeout': _int_to_str(timeout),
            'sharesnapshot': _to_str(snapshot),
        }
        if start_range is not None:
            _validate_and_format_range_headers(
                request,
                start_range,
                end_range,
                start_range_required=False,
                end_range_required=False)

        return self._perform_request(request, _convert_xml_to_ranges)
