Hello community,

here is the log from the commit of package python-azure-storage-file for 
openSUSE:Factory checked in at 2019-10-10 14:32:01
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-azure-storage-file (Old)
 and      /work/SRC/openSUSE:Factory/.python-azure-storage-file.new.2352 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-azure-storage-file"

Thu Oct 10 14:32:01 2019 rev:3 rq:735762 version:2.1.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-azure-storage-file/python-azure-storage-file.changes
      2019-05-14 13:38:56.520427135 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-azure-storage-file.new.2352/python-azure-storage-file.changes
    2019-10-10 14:32:03.796385782 +0200
@@ -1,0 +2,13 @@
+Fri Oct  4 12:21:54 UTC 2019 - John Paul Adrian Glaubitz 
<[email protected]>
+
+- New upstream release
+  + Version 2.1.0
+  + For detailed information about changes see the
+    HISTORY.txt file provided with this package
+- Drop patches to support older versions of setuptools as
+  SLE-12 is now shipping with a recent enough version
+  + asf_drop-compatible-releases-operator.patch
+  + asf_drop-extras-require.patch
+- Update Requires from setup.py
+
+-------------------------------------------------------------------

Old:
----
  asf_drop-compatible-releases-operator.patch
  asf_drop-extras-require.patch
  azure-storage-file-1.4.0.tar.gz

New:
----
  azure-storage-file-2.1.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-azure-storage-file.spec ++++++
--- /var/tmp/diff_new_pack.SbrCS8/_old  2019-10-10 14:32:04.640383814 +0200
+++ /var/tmp/diff_new_pack.SbrCS8/_new  2019-10-10 14:32:04.668383749 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-azure-storage-file
-Version:        1.4.0
+Version:        2.1.0
 Release:        0
 Summary:        Microsoft Azure Storage File Client Library for Python
 License:        MIT
@@ -26,8 +26,6 @@
 Url:            https://github.com/Azure/azure-sdk-for-python
 Source:         
https://files.pythonhosted.org/packages/source/a/azure-storage-file/azure-storage-file-%{version}.tar.gz
 Source1:        LICENSE.txt
-Patch1:         asf_drop-compatible-releases-operator.patch
-Patch2:         asf_drop-extras-require.patch
 BuildRequires:  %{python_module azure-nspkg >= 3.0.0}
 BuildRequires:  %{python_module azure-storage-nspkg >= 3.0.0}
 BuildRequires:  %{python_module setuptools}
@@ -35,8 +33,8 @@
 BuildRequires:  python-rpm-macros
 Requires:       python-azure-common >= 1.1.5
 Requires:       python-azure-nspkg >= 3.0.0
-Requires:       python-azure-storage-common < 2.0.0
-Requires:       python-azure-storage-common >= 1.4
+Requires:       python-azure-storage-common < 3.0.0
+Requires:       python-azure-storage-common >= 2.1.0
 Requires:       python-azure-storage-nspkg >= 3.0.0
 Conflicts:      python-azure-sdk <= 2.0.0
 Conflicts:      python-azure-storage <= 0.36.0
@@ -53,8 +51,6 @@
 
 %prep
 %setup -q -n azure-storage-file-%{version}
-%patch1 -p1
-%patch2 -p1
 
 %build
 install -m 644 %{SOURCE1} %{_builddir}/azure-storage-file-%{version}

++++++ azure-storage-file-1.4.0.tar.gz -> azure-storage-file-2.1.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure-storage-file-1.4.0/PKG-INFO 
new/azure-storage-file-2.1.0/PKG-INFO
--- old/azure-storage-file-1.4.0/PKG-INFO       2018-11-10 03:01:24.000000000 
+0100
+++ new/azure-storage-file-2.1.0/PKG-INFO       2019-08-02 06:12:46.000000000 
+0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: azure-storage-file
-Version: 1.4.0
+Version: 2.1.0
 Summary: Microsoft Azure Storage File Client Library for Python
 Home-page: https://github.com/Azure/azure-storage-python
 Author: Microsoft Corporation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure-storage-file-1.4.0/azure/storage/file/__init__.py 
new/azure-storage-file-2.1.0/azure/storage/file/__init__.py
--- old/azure-storage-file-1.4.0/azure/storage/file/__init__.py 2018-09-10 
23:24:14.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/__init__.py 2019-08-01 
23:45:21.000000000 +0200
@@ -17,4 +17,7 @@
     SharePermissions,
     FilePermissions,
     DeleteSnapshot,
+    SMBProperties,
+    NTFSAttributes,
 )
+from ._constants import __version__
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure-storage-file-1.4.0/azure/storage/file/_constants.py 
new/azure-storage-file-2.1.0/azure/storage/file/_constants.py
--- old/azure-storage-file-1.4.0/azure/storage/file/_constants.py       
2018-11-10 00:24:52.000000000 +0100
+++ new/azure-storage-file-2.1.0/azure/storage/file/_constants.py       
2019-08-02 06:03:05.000000000 +0200
@@ -5,7 +5,7 @@
 # --------------------------------------------------------------------------
 
 __author__ = 'Microsoft Corp. <[email protected]>'
-__version__ = '1.4.0'
+__version__ = '2.1.0'
 
 # x-ms-version for storage service.
-X_MS_VERSION = '2018-03-28'
+X_MS_VERSION = '2019-02-02'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure-storage-file-1.4.0/azure/storage/file/_deserialization.py 
new/azure-storage-file-2.1.0/azure/storage/file/_deserialization.py
--- old/azure-storage-file-1.4.0/azure/storage/file/_deserialization.py 
2018-09-10 23:24:14.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/_deserialization.py 
2019-07-29 21:41:49.000000000 +0200
@@ -13,6 +13,7 @@
     Share,
     Directory,
     File,
+    Handle,
     FileProperties,
     FileRange,
     ShareProperties,
@@ -31,6 +32,7 @@
     _to_str,
 )
 
+
 def _parse_snapshot_share(response, name):
     '''
     Extracts snapshot return header.
@@ -39,6 +41,7 @@
 
     return _parse_share(response, name, snapshot)
 
+
 def _parse_share(response, name, snapshot=None):
     if response is None:
         return None
@@ -57,6 +60,24 @@
     return Directory(name, props, metadata)
 
 
+def _parse_permission_key(response):
+    '''
+    Extracts out file permission key
+    '''
+
+    if response is None or response.headers is None:
+        return None
+    return response.headers.get('x-ms-file-permission-key', None)
+
+
+def _parse_permission(response):
+    '''
+    Extracts out file permission
+    '''
+
+    return response.body
+
+
 def _parse_file(response, name, validate_content=False):
     if response is None:
         return None
@@ -197,6 +218,70 @@
     return entries
 
 
+def _convert_xml_to_handles(response):
+    """
+    <?xml version="1.0" encoding="utf-8"?>
+    <EnumerationResults>
+        <Entries>
+            <Handle>
+                <HandleId>21123954401</HandleId>
+                <Path />
+                <FileId>0</FileId>
+                <ParentId>0</ParentId>
+                <SessionId>9385737614310506553</SessionId>
+                <ClientIp>167.220.2.92:27553</ClientIp>
+                <OpenTime>Fri, 03 May 2019 05:59:43 GMT</OpenTime>
+            </Handle>
+            ...
+        </Entries>
+        <NextMarker />
+    </EnumerationResults>'
+    """
+    if response is None or response.body is None:
+        return None
+
+    entries = _list()
+    list_element = ETree.fromstring(response.body)
+
+    # Set next marker
+    next_marker = list_element.findtext('NextMarker') or None
+    setattr(entries, 'next_marker', next_marker)
+
+    handles_list_element = list_element.find('Entries')
+
+    for handle_element in handles_list_element.findall('Handle'):
+        # Name element
+        handle = Handle()
+        handle.handle_id = handle_element.findtext('HandleId')
+        handle.path = handle_element.findtext('Path')
+        handle.file_id = handle_element.findtext('FileId')
+        handle.parent_id = handle_element.findtext('ParentId')
+        handle.session_id = handle_element.findtext('SessionId')
+        handle.client_ip = handle_element.findtext('ClientIp')
+        handle.open_time = parser.parse(handle_element.findtext('OpenTime'))
+
+        last_connect_time_string = handle_element.findtext('LastReconnectTime')
+        if last_connect_time_string is not None:
+            handle.last_reconnect_time = parser.parse(last_connect_time_string)
+
+        # Add file to list
+        entries.append(handle)
+
+    return entries
+
+
+def _parse_close_handle_response(response):
+    if response is None or response.body is None:
+        return 0
+
+    results = _list()
+    results.append(int(response.headers['x-ms-number-of-handles-closed']))
+
+    next_marker = None if 'x-ms-marker' not in response.headers else 
response.headers['x-ms-marker']
+    setattr(results, 'next_marker', next_marker)
+    return results
+
+
 def _convert_xml_to_ranges(response):
     '''
     <?xml version="1.0" encoding="utf-8"?>
@@ -231,11 +316,11 @@
     '''
     <?xml version="1.0" encoding="utf-8"?>
     <ShareStats>
-       <ShareUsage>15</ShareUsage>
+       <ShareUsageBytes>15</ShareUsageBytes>
     </ShareStats>
     '''
     if response is None or response.body is None:
         return None
 
     share_stats_element = ETree.fromstring(response.body)
-    return int(share_stats_element.findtext('ShareUsage'))
+    return int(share_stats_element.findtext('ShareUsageBytes'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure-storage-file-1.4.0/azure/storage/file/_serialization.py 
new/azure-storage-file-2.1.0/azure/storage/file/_serialization.py
--- old/azure-storage-file-1.4.0/azure/storage/file/_serialization.py   
2018-09-10 23:24:14.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/_serialization.py   
2019-08-02 05:03:23.000000000 +0200
@@ -3,12 +3,17 @@
 # Licensed under the MIT License. See License.txt in the project root for
 # license information.
 # --------------------------------------------------------------------------
+from sys import getsizeof
+
 from azure.storage.common._common_conversion import _str
 from azure.storage.common._error import (
     _validate_not_none,
     _ERROR_START_END_NEEDED_FOR_MD5,
     _ERROR_RANGE_TOO_LARGE_FOR_MD5,
 )
+_ERROR_TOO_MANY_FILE_PERMISSIONS = 'file_permission and file_permission_key 
should not be set at the same time'
+_FILE_PERMISSION_TOO_LONG = 'Size of file_permission is too large. 
file_permission should be <=8KB, else' \
+                            'please use file_permission_key'
 
 
 def _get_path(share_name=None, directory_name=None, file_name=None):
@@ -42,7 +47,7 @@
 
 
 def _validate_and_format_range_headers(request, start_range, end_range, 
start_range_required=True,
-                                       end_range_required=True, 
check_content_md5=False):
+                                       end_range_required=True, 
check_content_md5=False, is_source=False):
     # If end range is provided, start range must be provided
     if start_range_required or end_range is not None:
         _validate_not_none('start_range', start_range)
@@ -51,10 +56,11 @@
 
     # Format based on whether end_range is present
     request.headers = request.headers or {}
+    header_name = 'x-ms-source-range' if is_source else 'x-ms-range'
     if end_range is not None:
-        request.headers['x-ms-range'] = 'bytes={0}-{1}'.format(start_range, 
end_range)
+        request.headers[header_name] = 'bytes={0}-{1}'.format(start_range, 
end_range)
     elif start_range is not None:
-        request.headers['x-ms-range'] = 'bytes={0}-'.format(start_range)
+        request.headers[header_name] = 'bytes={0}-'.format(start_range)
 
     # Content MD5 can only be provided for a complete range less than 4MB in 
size
     if check_content_md5:
@@ -64,3 +70,26 @@
             raise ValueError(_ERROR_RANGE_TOO_LARGE_FOR_MD5)
 
         request.headers['x-ms-range-get-content-md5'] = 'true'
+
+
+def _validate_and_return_file_permission(file_permission, file_permission_key, 
default_permission):
+    # if file_permission and file_permission_key are both empty, then use the 
default_permission
+    # value as file permission, file_permission size should be <= 8KB, else 
file permission_key should be used
+    empty_file_permission = file_permission is None or len(file_permission) == 0
+    empty_file_permission_key = file_permission_key is None or 
len(file_permission_key) == 0
+    file_permission_size_too_big = False if file_permission is None \
+        else len(str(file_permission).encode('utf-8')) > 8 * 1024
+
+    if file_permission_size_too_big:
+        raise ValueError(_FILE_PERMISSION_TOO_LONG)
+
+    if empty_file_permission:
+        if empty_file_permission_key:
+            return default_permission
+        else:
+            return None
+
+    if empty_file_permission_key:
+        return file_permission
+
+    raise ValueError(_ERROR_TOO_MANY_FILE_PERMISSIONS)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure-storage-file-1.4.0/azure/storage/file/fileservice.py 
new/azure-storage-file-2.1.0/azure/storage/file/fileservice.py
--- old/azure-storage-file-1.4.0/azure/storage/file/fileservice.py      
2018-10-23 23:30:56.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/fileservice.py      
2019-08-01 23:43:12.000000000 +0200
@@ -1,9 +1,14 @@
+# 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
+from datetime import datetime
+
+import math
 from os import path
 
 from azure.common import AzureHttpError
@@ -61,22 +66,24 @@
 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,
-)
+    _parse_permission_key, _parse_permission)
 from ._download_chunking import _download_file_chunks
 from ._serialization import (
     _get_path,
     _validate_and_format_range_headers,
-)
+    _validate_and_return_file_permission)
 from ._upload_chunking import _upload_file_chunks
 from .models import (
     FileProperties,
-)
+    SMBProperties)
 
 from ._constants import (
     X_MS_VERSION,
@@ -89,6 +96,8 @@
 _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:
@@ -223,7 +232,7 @@
             )
 
         if sas_token:
-            url += '?' + sas_token
+            url += (sas_token if sas_token.startswith('?') else '?' + 
sas_token)
 
         return url
 
@@ -874,7 +883,7 @@
         rounded up to the nearest gigabyte.
         
         Note that this value may not include all recently created
-        or recently resized files.
+        or recently re-sized files.
 
         :param str share_name:
             Name of existing share.
@@ -894,6 +903,34 @@
             '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):
@@ -945,7 +982,7 @@
             return True
 
     def create_directory(self, share_name, directory_name, metadata=None,
-                         fail_on_exist=False, timeout=None):
+                         fail_on_exist=False, timeout=None, 
file_permission=None, smb_properties=SMBProperties()):
         '''
         Creates a new directory under the specified share or parent directory. 
         If the directory with the same name already exists, the operation fails
@@ -964,6 +1001,10 @@
         :param bool fail_on_exist:
             specify whether to throw an exception when the directory exists.
             False by default.
+        :param str file_permission:
+            File permission, a portable SDDL
+        :param ~azure.storage.file.models.SMBProperties smb_properties:
+            Sets the SMB related file properties
         :param int timeout:
             The timeout parameter is expressed in seconds.
         :return: True if directory is created, False if directory already 
exists.
@@ -971,6 +1012,17 @@
         '''
         _validate_not_none('share_name', share_name)
         _validate_not_none('directory_name', directory_name)
+        current_time = datetime.utcnow()
+        if smb_properties.ntfs_attributes is None:
+            smb_properties.ntfs_attributes = 'Directory'
+        if smb_properties.creation_time is None:
+            smb_properties.creation_time = current_time
+        if smb_properties.last_write_time is None:
+            smb_properties.last_write_time = current_time
+        file_permission = _validate_and_return_file_permission(file_permission,
+                                                               
smb_properties.permission_key,
+                                                               'Inherit')
+
         request = HTTPRequest()
         request.method = 'PUT'
         request.host_locations = self._get_host_locations()
@@ -980,6 +1032,8 @@
             'timeout': _int_to_str(timeout),
         }
         _add_metadata_headers(metadata, request)
+        request.headers.update({'x-ms-file-permission': file_permission})
+        request.headers.update(smb_properties._to_request_headers())
 
         if not fail_on_exist:
             try:
@@ -992,6 +1046,29 @@
             self._perform_request(request)
             return True
 
+    def set_directory_properties(self, share_name, directory_name, 
file_permission=None,
+                                 smb_properties=SMBProperties(), timeout=None):
+        """
+
+        :param share_name:
+            Name of the share
+        :param directory_name:
+            Name of the directory
+        :param str file_permission:
+            File permission, a portable SDDL
+        :param ~azure.storage.file.models.SMBProperties smb_properties:
+            Sets the SMB related file properties
+        :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 = 
self._get_basic_set_file_or_directory_properties_http_request(share_name, 
directory_name, None,
+                                                                               
 file_permission, smb_properties,
+                                                                               
 timeout)
+        request.query.update({'restype': 'directory'})
+        self._perform_request(request)
+
     def delete_directory(self, share_name, directory_name,
                          fail_not_exist=False, timeout=None):
         '''
@@ -1228,6 +1305,187 @@
         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
@@ -1306,8 +1564,7 @@
             _dont_fail_not_exist(ex)
             return False
 
-    def resize_file(self, share_name, directory_name,
-                    file_name, content_length, timeout=None):
+    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
@@ -1327,22 +1584,14 @@
         _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)
-        }
+        request = 
self._get_basic_set_file_or_directory_properties_http_request(share_name, 
directory_name, file_name,
+                                                                               
 None, SMBProperties(), timeout)
+        request.headers.update({'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):
+                            content_settings, timeout=None, 
file_permission=None, smb_properties=SMBProperties()):
         '''
         Sets system properties on the file. If one property is set for the
         content_settings, all properties will be overriden.
@@ -1355,12 +1604,36 @@
             Name of existing file.
         :param ~azure.storage.file.models.ContentSettings content_settings:
             ContentSettings object used to set the file properties.
+        :param str file_permission:
+            File permission, a portable SDDL
+        :param ~azure.storage.file.models.SMBProperties smb_properties:
+            Sets the SMB related 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 = 
self._get_basic_set_file_or_directory_properties_http_request(share_name, 
directory_name, file_name,
+                                                                               
 file_permission, smb_properties,
+                                                                               
 timeout)
+        request.headers.update(content_settings._to_headers())
+
+        self._perform_request(request)
+
+    def _get_basic_set_file_or_directory_properties_http_request(self, 
share_name, directory_name, file_name,
+                                                                 
file_permission, smb_properties, timeout):
+        common_default_value = 'Preserve'
+        if smb_properties.ntfs_attributes is None:
+            smb_properties.ntfs_attributes = common_default_value
+        if smb_properties.creation_time is None:
+            smb_properties.creation_time = common_default_value
+        if smb_properties.last_write_time is None:
+            smb_properties.last_write_time = common_default_value
+        file_permission = _validate_and_return_file_permission(file_permission,
+                                                               
smb_properties.permission_key,
+                                                               
common_default_value)
+
         request = HTTPRequest()
         request.method = 'PUT'
         request.host_locations = self._get_host_locations()
@@ -1369,9 +1642,10 @@
             'comp': 'properties',
             'timeout': _int_to_str(timeout),
         }
-        request.headers = content_settings._to_headers()
+        request.headers = {'x-ms-file-permission': file_permission}
+        request.headers.update(smb_properties._to_request_headers())
 
-        self._perform_request(request)
+        return request
 
     def get_file_metadata(self, share_name, directory_name, file_name, 
timeout=None, snapshot=None):
         '''
@@ -1553,8 +1827,8 @@
         self._perform_request(request)
 
     def create_file(self, share_name, directory_name, file_name,
-                    content_length, content_settings=None, metadata=None,
-                    timeout=None):
+                    content_length, content_settings=None, metadata=None, 
timeout=None,
+                    file_permission=None, smb_properties=SMBProperties()):
         '''
         Creates a new file.
 
@@ -1575,12 +1849,27 @@
         :param metadata:
             Name-value pairs associated with the file as metadata.
         :type metadata: dict(str, str)
+        :param str file_permission:
+            File permission, a portable SDDL
+        :param ~azure.storage.file.models.SMBProperties smb_properties:
+            Sets the SMB related 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_length', content_length)
+        current_time = datetime.utcnow()
+        if smb_properties.ntfs_attributes is None:
+            smb_properties.ntfs_attributes = 'Archive'
+        if smb_properties.creation_time is None:
+            smb_properties.creation_time = current_time
+        if smb_properties.last_write_time is None:
+            smb_properties.last_write_time = current_time
+
+        file_permission = _validate_and_return_file_permission(file_permission,
+                                                               
smb_properties.permission_key,
+                                                               'Inherit')
         request = HTTPRequest()
         request.method = 'PUT'
         request.host_locations = self._get_host_locations()
@@ -1588,18 +1877,20 @@
         request.query = {'timeout': _int_to_str(timeout)}
         request.headers = {
             'x-ms-content-length': _to_str(content_length),
-            'x-ms-type': 'file'
+            'x-ms-type': 'file',
+            'x-ms-file-permission': file_permission
         }
         _add_metadata_headers(metadata, request)
         if content_settings is not None:
             request.headers.update(content_settings._to_headers())
+        request.headers.update(smb_properties._to_request_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):
+                              max_connections=2, file_permission=None, 
smb_properties=SMBProperties(), 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.
@@ -1631,6 +1922,10 @@
         :type progress_callback: func(current, total)
         :param int max_connections:
             Maximum number of parallel connections to use.
+        :param str file_permission:
+            File permission, a portable SDDL
+        :param ~azure.storage.file.models.SMBProperties smb_properties:
+            Sets the SMB related file properties
         :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 
@@ -1645,11 +1940,12 @@
             self.create_file_from_stream(
                 share_name, directory_name, file_name, stream,
                 count, content_settings, metadata, validate_content, 
progress_callback,
-                max_connections, timeout)
+                max_connections, file_permission=file_permission, 
smb_properties=smb_properties, timeout=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):
+                              metadata=None, validate_content=False, 
timeout=None, file_permission=None,
+                              smb_properties=SMBProperties()):
         '''
         Creates a new file from str/unicode, or updates the content of an
         existing file, with automatic chunking and progress notifications.
@@ -1676,6 +1972,10 @@
             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 str file_permission:
+            File permission, a portable SDDL
+        :param ~azure.storage.file.models.SMBProperties smb_properties:
+            Sets the SMB related file properties
         :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 
@@ -1692,13 +1992,14 @@
         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)
+            validate_content=validate_content, 
file_permission=file_permission, smb_properties=smb_properties,
+            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):
+            validate_content=False, progress_callback=None, max_connections=2, 
timeout=None,
+            file_permission=None, smb_properties=SMBProperties()):
         '''
         Creates a new file from an array of bytes, or updates the content
         of an existing file, with automatic chunking and progress
@@ -1736,6 +2037,10 @@
         :type progress_callback: func(current, total)
         :param int max_connections:
             Maximum number of parallel connections to use.
+        :param str file_permission:
+            File permission, a portable SDDL
+        :param ~azure.storage.file.models.SMBProperties smb_properties:
+            Sets the SMB related file properties
         :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 
@@ -1758,12 +2063,13 @@
         self.create_file_from_stream(
             share_name, directory_name, file_name, stream, count,
             content_settings, metadata, validate_content, progress_callback,
-            max_connections, timeout)
+            max_connections, file_permission=file_permission, 
smb_properties=smb_properties, timeout=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):
+            progress_callback=None, max_connections=2, timeout=None,
+            file_permission=None, smb_properties=SMBProperties()):
         '''
         Creates a new file from a file/stream, or updates the content of an
         existing file, with automatic chunking and progress notifications.
@@ -1799,6 +2105,10 @@
         :param int max_connections:
             Maximum number of parallel connections to use. Note that parallel 
upload 
             requires the stream to be seekable.
+        :param str file_permission:
+            File permission, a portable SDDL
+        :param ~azure.storage.file.models.SMBProperties smb_properties:
+            Sets the SMB related file properties
         :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 
@@ -1819,7 +2129,9 @@
             count,
             content_settings,
             metadata,
-            timeout
+            file_permission=file_permission,
+            smb_properties=smb_properties,
+            timeout=timeout
         )
 
         _upload_file_chunks(
@@ -2366,9 +2678,76 @@
         :param int timeout:
             The timeout parameter is expressed in seconds.
         '''
+        request = self._get_basic_update_file_http_request(share_name, 
directory_name, file_name, timeout=timeout)
+
+        _validate_not_none('data', data)
+        _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 update_range_from_file_url(self, share_name, directory_name, 
file_name, start_range, end_range, source,
+                                   source_start_range, timeout=None):
+        '''
+        Writes the bytes from one Azure File endpoint into the specified range 
of another Azure File endpoint.
+
+        :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 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 str 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 int source_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 timeout:
+            The timeout parameter is expressed in seconds.
+        '''
+
+        request = self._get_basic_update_file_http_request(share_name, 
directory_name, file_name, timeout=timeout)
+
+        _validate_not_none('source', source)
+        _validate_and_format_range_headers(request, start_range, end_range)
+        _validate_and_format_range_headers(request,
+                                           source_start_range,
+                                           
source_start_range+(end_range-start_range),
+                                           is_source=True)
+
+        request.headers.update({
+            'x-ms-copy-source': _to_str(source),
+            'Content-Length': _int_to_str(0)
+        })
+
+        self._perform_request(request)
+
+    def _get_basic_update_file_http_request(self, share_name, directory_name, 
file_name, timeout=None):
         _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()
@@ -2380,15 +2759,8 @@
         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)
+        return request
 
     def clear_range(self, share_name, directory_name, file_name, start_range,
                     end_range, timeout=None):
@@ -2480,3 +2852,65 @@
                 end_range_required=False)
 
         return self._perform_request(request, _convert_xml_to_ranges)
+
+    def create_permission_for_share(self, share_name, file_permission, 
timeout=None):
+        """
+        Create a permission(a security descriptor) at the share level.
+        This 'permission' can be used for the files/directories in the share.
+        If a 'permission' already exists, it shall return the key of it, else
+        creates a new permission at the share level and return its key.
+
+        :param share_name:
+            Name of share.
+        :param file_permission:
+            File permission, a Portable SDDL
+        :param timeout:
+            The timeout parameter is expressed in seconds.
+        :returns a file permission key
+        :rtype str
+        """
+        _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': 'filepermission',
+            'timeout': _int_to_str(timeout),
+        }
+        request.body = file_permission
+        return self._perform_request(request, parser=_parse_permission_key)
+
+    def get_permission_for_share(self, share_name, file_permission_key, 
timeout=None):
+        """
+        Create a permission(a security descriptor) at the share level.
+        This 'permission' can be used for the files/directories in the share.
+        If a 'permission' already exists, it shall return the key of it, else
+        creates a new permission at the share level and return its key.
+
+        :param share_name:
+            Name of share.
+        :param file_permission_key:
+            Key of the file permission to retrieve
+        :param timeout:
+            The timeout parameter is expressed in seconds.
+        :returns a file permission(a portable SDDL)
+        :rtype 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': 'filepermission',
+            'timeout': _int_to_str(timeout),
+        }
+        request.headers = {
+            'x-ms-file-permission-key': file_permission_key
+        }
+        request.body = None
+
+        return self._perform_request(request, parser=_parse_permission)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure-storage-file-1.4.0/azure/storage/file/models.py 
new/azure-storage-file-2.1.0/azure/storage/file/models.py
--- old/azure-storage-file-1.4.0/azure/storage/file/models.py   2018-09-10 
23:24:14.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/models.py   2019-07-29 
21:41:49.000000000 +0200
@@ -85,12 +85,15 @@
         conditionally.
     :ivar bool server_encrypted:
         Set to true if the directory metadata is encrypted on the server.
+    :ivar ~azure.storage.file.models.SMBProperties smb_properties:
+        SMB related file properties
     '''
 
     def __init__(self):
         self.last_modified = None
         self.etag = None
         self.server_encrypted = None
+        self.smb_properties = SMBProperties()
 
 
 class File(object):
@@ -139,8 +142,12 @@
         Stores all the content settings for the file.
     :ivar ~azure.storage.file.models.CopyProperties copy:
         Stores all the copy properties for the file.
-    ivar bool server_encrypted:
+    :ivar bool server_encrypted:
         Set to true if the file data and application metadata are completely 
encrypted.
+    :ivar ~azure.storage.file.models.SMBProperties smb_properties:
+        SMB related file properties
+    :ivar ~azure.storage.file.models.LeaseProperties lease:
+        Stores all the lease information for the file.
     '''
 
     def __init__(self):
@@ -151,6 +158,111 @@
         self.content_settings = ContentSettings()
         self.copy = CopyProperties()
         self.server_encrypted = None
+        self.smb_properties = SMBProperties()
+        self.lease = LeaseProperties()
+
+
+class SMBProperties(object):
+    """
+    SMB related properties to get/set for for file/directory
+
+    :ivar str or :class:`~azure.storage.file.models.NTFSAttributes` 
ntfs_attributes:
+        The file system attributes for files and directories.
+        If not set, indicates preservation of existing values.
+        Here is an example for when the var type is str: 'Temporary|Archive'
+    :ivar str or datetime creation_time:
+        When the File or Directory was created.
+        If it is a string type, time should have 7 decimal digits, eg. 
'2019-07-07T02:52:46.5540162Z'
+    :ivar str or datetime last_write_time:
+        When the File or Directory was last modified. eg. 
'2019-07-07T02:52:46.5540162Z'
+        If it is a string type, time should have 7 decimal digits, eg. 
'2019-07-07T02:52:46.5540162Z'
+    :ivar str permission_key:
+        The file's File Permission Key
+    :ivar str change_time:
+        When the File was last changed. This is what will be returned by 
service. Users don't need to specify.
+    :ivar str file_id:
+        The Id of this directory. This is what will be returned by service. 
Users don't need to specify.
+    :ivar str parent_id:
+        The Id of this directory's parent. This is what will be returned by 
service. Users don't need to specify.
+    """
+    def __init__(self, ntfs_attributes=None, creation_time=None, 
last_write_time=None, permission_key=None):
+        self.ntfs_attributes = ntfs_attributes
+        self.creation_time = creation_time
+        self.last_write_time = last_write_time
+        self.permission_key = permission_key
+        self.change_time = None
+        self.file_id = None
+        self.parent_id = None
+
+    def _to_request_headers(self):
+        creation_time = self.creation_time if isinstance(self.creation_time, 
str) \
+            else self.creation_time.isoformat() + '0Z'
+        last_write_time = self.last_write_time if 
isinstance(self.last_write_time, str) \
+            else self.last_write_time.isoformat() + '0Z'
+        return {
+            'x-ms-file-attributes': _to_str(self.ntfs_attributes),
+            'x-ms-file-creation-time': creation_time,
+            'x-ms-file-last-write-time': last_write_time,
+            'x-ms-file-permission-key': _to_str(self.permission_key)
+        }
+
+
+class LeaseProperties(object):
+    '''
+    File Lease Properties.
+
+    :ivar str status:
+        The lease status of the file.
+        Possible values: locked|unlocked
+    :ivar str state:
+        Lease state of the file.
+        Possible values: available|leased|expired|breaking|broken
+    :ivar str duration:
+        When a file is leased, specifies whether the lease is of infinite or 
fixed duration.
+    '''
+
+    def __init__(self):
+        self.status = None
+        self.state = None
+        self.duration = None
+
+
+class Handle(object):
+    """
+    Represents a file handle.
+
+    :ivar str handle_id:
+        Used to identify handle.
+    :ivar str path:
+        Used to identify the name of the object for which the handle is open.
+    :ivar str file_id:
+        Uniquely identifies the file.
+        This is useful when renames are happening as the file ID does not 
change.
+    :ivar str parent_id:
+        Uniquely identifies the parent directory.
+        This is useful when renames are happening as the parent ID does not 
change.
+    :ivar str session_id:
+        Session ID in context of which the file handle was opened.
+    :ivar str client_ip:
+        Used to identify client that has opened the handle.
+        The field is included only if client IP is known by the service.
+    :ivar datetime open_time:
+        Used to decide if handle may have been leaked.
+    :ivar datetime last_reconnect_time:
+        Used to decide if handle was reopened after client/server disconnect 
due to networking or other faults.
+        The field is included only if disconnect event occurred and handle was 
reopened.
+    """
+
+    def __init__(self, handle_id=None, path=None, file_id=None, 
parent_id=None, session_id=None,
+                 client_ip=None, open_time=None, last_reconnect_time=None):
+        self.handle_id = handle_id
+        self.path = path
+        self.file_id = file_id
+        self.parent_id = parent_id
+        self.session_id = session_id
+        self.client_ip = client_ip
+        self.open_time = open_time
+        self.last_reconnect_time = last_reconnect_time
 
 
 class ContentSettings(object):
@@ -405,3 +517,77 @@
 SharePermissions.LIST = SharePermissions(list=True)
 SharePermissions.READ = SharePermissions(read=True)
 SharePermissions.WRITE = SharePermissions(write=True)
+
+
+class NTFSAttributes(object):
+    """
+    Valid set of attributes to set for file or directory.
+    To set attribute for directory, 'Directory' should always be enabled 
except setting 'None' for directory.
+
+    :ivar bool read_only:
+        Enable/disable 'ReadOnly' attribute for DIRECTORY or FILE
+    :ivar bool hidden:
+        Enable/disable 'Hidden' attribute for DIRECTORY or FILE
+    :ivar bool system:
+        Enable/disable 'System' attribute for DIRECTORY or FILE
+    :ivar bool none:
+        Enable/disable 'None' attribute for DIRECTORY or FILE to clear all 
attributes of FILE/DIRECTORY
+    :ivar bool directory:
+        Enable/disable 'Directory' attribute for DIRECTORY
+    :ivar bool archive:
+        Enable/disable 'Archive' attribute for DIRECTORY or FILE
+    :ivar bool temporary:
+        Enable/disable 'Temporary' attribute for FILE
+    :ivar bool offline:
+        Enable/disable 'Offline' attribute for DIRECTORY or FILE
+    :ivar bool not_content_indexed:
+        Enable/disable 'NotContentIndexed' attribute for DIRECTORY or FILE
+    :ivar bool no_scrub_data:
+        Enable/disable 'NoScrubData' attribute for DIRECTORY or FILE
+    """
+    def __init__(self, read_only=False, hidden=False, system=False, 
none=False, directory=False, archive=False,
+                 temporary=False, offline=False, not_content_indexed=False, 
no_scrub_data=False, _str=None):
+        if not _str:
+            _str = ''
+        self.read_only = read_only or ('ReadOnly' in _str)
+        self.hidden = hidden or ('Hidden' in _str)
+        self.system = system or ('System' in _str)
+        self.none = none or ('None' in _str)
+        self.directory = directory or ('Directory' in _str)
+        self.archive = archive or ('Archive' in _str)
+        self.temporary = temporary or ('Temporary' in _str)
+        self.offline = offline or ('Offline' in _str)
+        self.not_content_indexed = not_content_indexed or ('NotContentIndexed' 
in _str)
+        self.no_scrub_data = no_scrub_data or ('NoScrubData' in _str)
+
+    def __or__(self, other):
+        return NTFSAttributes(_str=str(self) + str(other))
+
+    def __add__(self, other):
+        return NTFSAttributes(_str=str(self) + str(other))
+
+    def __str__(self):
+        concatenated_params = (('ReadOnly|' if self.read_only else '') +
+                               ('Hidden|' if self.hidden else '') +
+                               ('System|' if self.system else '') +
+                               ('None|' if self.none else '') +
+                               ('Directory|' if self.directory else '') +
+                               ('Archive|' if self.archive else '') +
+                               ('Temporary|' if self.temporary else '') +
+                               ('Offline|' if self.offline else '') +
+                               ('NotContentIndexed|' if 
self.not_content_indexed else '') +
+                               ('NoScrubData|' if self.no_scrub_data else ''))
+
+        return concatenated_params.strip('|')
+
+
+NTFSAttributes.READ_ONLY = NTFSAttributes(read_only=True)
+NTFSAttributes.HIDDEN = NTFSAttributes(hidden=True)
+NTFSAttributes.SYSTEM = NTFSAttributes(system=True)
+NTFSAttributes.NONE = NTFSAttributes(none=True)
+NTFSAttributes.DIRECTORY = NTFSAttributes(directory=True)
+NTFSAttributes.ARCHIVE = NTFSAttributes(archive=True)
+NTFSAttributes.TEMPORARY = NTFSAttributes(temporary=True)
+NTFSAttributes.OFFLINE = NTFSAttributes(offline=True)
+NTFSAttributes.NOT_CONTENT_INDEXED = NTFSAttributes(not_content_indexed=True)
+NTFSAttributes.NO_SCRUB_DATA = NTFSAttributes(no_scrub_data=True)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure-storage-file-1.4.0/azure/storage/file/sharedaccesssignature.py 
new/azure-storage-file-2.1.0/azure/storage/file/sharedaccesssignature.py
--- old/azure-storage-file-1.4.0/azure/storage/file/sharedaccesssignature.py    
2018-10-17 22:48:05.000000000 +0200
+++ new/azure-storage-file-2.1.0/azure/storage/file/sharedaccesssignature.py    
2019-06-07 09:59:17.000000000 +0200
@@ -7,6 +7,8 @@
 from azure.storage.common.sharedaccesssignature import (
     SharedAccessSignature,
     _SharedAccessHelper,
+    _QueryStringConstants,
+    _sign_string,
 )
 from azure.storage.common._common_conversion import (
     _to_str,
@@ -103,14 +105,14 @@
             resource_path += '/' + _to_str(directory_name)
         resource_path += '/' + _to_str(file_name)
 
-        sas = _SharedAccessHelper()
+        sas = _FileSharedAccessHelper()
         sas.add_base(permission, expiry, start, ip, protocol, 
self.x_ms_version)
         sas.add_id(id)
         sas.add_resource('f')
         sas.add_override_response_headers(cache_control, content_disposition,
                                           content_encoding, content_language,
                                           content_type)
-        sas.add_resource_signature(self.account_name, self.account_key, 
'file', resource_path)
+        sas.add_resource_signature(self.account_name, self.account_key, 
resource_path)
 
         return sas.get_token()
 
@@ -176,13 +178,52 @@
             Response header value for Content-Type when resource is accessed
             using this shared access signature.
         '''
-        sas = _SharedAccessHelper()
+        sas = _FileSharedAccessHelper()
         sas.add_base(permission, expiry, start, ip, protocol, 
self.x_ms_version)
         sas.add_id(id)
         sas.add_resource('s')
         sas.add_override_response_headers(cache_control, content_disposition,
                                           content_encoding, content_language,
                                           content_type)
-        sas.add_resource_signature(self.account_name, self.account_key, 
'file', share_name)
+        sas.add_resource_signature(self.account_name, self.account_key, 
share_name)
 
         return sas.get_token()
+
+
+class _FileSharedAccessHelper(_SharedAccessHelper):
+    def __init__(self):
+        super(_FileSharedAccessHelper, self).__init__()
+
+    def add_resource_signature(self, account_name, account_key, path):
+        def get_value_to_append(query):
+            return_value = self.query_dict.get(query) or ''
+            return return_value + '\n'
+
+        if path[0] != '/':
+            path = '/' + path
+
+        canonicalized_resource = '/file/' + account_name + path + '\n'
+
+        # Form the string to sign from shared_access_policy and canonicalized
+        # resource. The order of values is important.
+        string_to_sign = \
+            (get_value_to_append(_QueryStringConstants.SIGNED_PERMISSION) +
+             get_value_to_append(_QueryStringConstants.SIGNED_START) +
+             get_value_to_append(_QueryStringConstants.SIGNED_EXPIRY) +
+             canonicalized_resource +
+             get_value_to_append(_QueryStringConstants.SIGNED_IDENTIFIER) +
+             get_value_to_append(_QueryStringConstants.SIGNED_IP) +
+             get_value_to_append(_QueryStringConstants.SIGNED_PROTOCOL) +
+             get_value_to_append(_QueryStringConstants.SIGNED_VERSION) +
+             get_value_to_append(_QueryStringConstants.SIGNED_CACHE_CONTROL) +
+             
get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_DISPOSITION) +
+             
get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_ENCODING) +
+             
get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_LANGUAGE) +
+             get_value_to_append(_QueryStringConstants.SIGNED_CONTENT_TYPE))
+
+        # remove the trailing newline
+        if string_to_sign[-1] == '\n':
+            string_to_sign = string_to_sign[:-1]
+
+        self._add_query(_QueryStringConstants.SIGNED_SIGNATURE,
+                        _sign_string(account_key, string_to_sign))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure-storage-file-1.4.0/azure_storage_file.egg-info/PKG-INFO 
new/azure-storage-file-2.1.0/azure_storage_file.egg-info/PKG-INFO
--- old/azure-storage-file-1.4.0/azure_storage_file.egg-info/PKG-INFO   
2018-11-10 03:01:24.000000000 +0100
+++ new/azure-storage-file-2.1.0/azure_storage_file.egg-info/PKG-INFO   
2019-08-02 06:12:46.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: azure-storage-file
-Version: 1.4.0
+Version: 2.1.0
 Summary: Microsoft Azure Storage File Client Library for Python
 Home-page: https://github.com/Azure/azure-storage-python
 Author: Microsoft Corporation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/azure-storage-file-1.4.0/azure_storage_file.egg-info/requires.txt 
new/azure-storage-file-2.1.0/azure_storage_file.egg-info/requires.txt
--- old/azure-storage-file-1.4.0/azure_storage_file.egg-info/requires.txt       
2018-11-10 03:01:24.000000000 +0100
+++ new/azure-storage-file-2.1.0/azure_storage_file.egg-info/requires.txt       
2019-08-02 06:12:46.000000000 +0200
@@ -1,5 +1,5 @@
 azure-common>=1.1.5
-azure-storage-common~=1.4
+azure-storage-common~=2.1
 
 [:python_version<'3.0']
 futures
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/azure-storage-file-1.4.0/setup.py 
new/azure-storage-file-2.1.0/setup.py
--- old/azure-storage-file-1.4.0/setup.py       2018-11-10 00:24:52.000000000 
+0100
+++ new/azure-storage-file-2.1.0/setup.py       2019-08-02 06:03:32.000000000 
+0200
@@ -44,7 +44,7 @@
 
 setup(
     name='azure-storage-file',
-    version='1.4.0',
+    version='2.1.0',
     description='Microsoft Azure Storage File Client Library for Python',
     long_description=open('README.rst', 'r').read(),
     license='MIT License',
@@ -72,7 +72,7 @@
     ]),
     install_requires=[
         'azure-common>=1.1.5',
-        'azure-storage-common~=1.4'
+        'azure-storage-common~=2.1'
     ],
     extras_require={
         ":python_version<'3.0'": ['futures'],


Reply via email to