1# -------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for
4# license information.
5# --------------------------------------------------------------------------
6
7import uuid
8
9from typing import (  # pylint: disable=unused-import
10    Union, Optional, Any, TypeVar, TYPE_CHECKING
11)
12
13from azure.core.tracing.decorator import distributed_trace
14
15from ._shared.response_handlers import return_response_headers, process_storage_error
16from ._generated.models import StorageErrorException
17from ._generated.operations import FileOperations, ShareOperations
18
19if TYPE_CHECKING:
20    from datetime import datetime
21    ShareFileClient = TypeVar("ShareFileClient")
22    ShareClient = TypeVar("ShareClient")
23
24
25class ShareLeaseClient(object):
26    """Creates a new ShareLeaseClient.
27
28    This client provides lease operations on a ShareClient or ShareFileClient.
29
30    :ivar str id:
31        The ID of the lease currently being maintained. This will be `None` if no
32        lease has yet been acquired.
33    :ivar str etag:
34        The ETag of the lease currently being maintained. This will be `None` if no
35        lease has yet been acquired or modified.
36    :ivar ~datetime.datetime last_modified:
37        The last modified timestamp of the lease currently being maintained.
38        This will be `None` if no lease has yet been acquired or modified.
39
40    :param client:
41        The client of the file or share to lease.
42    :type client: ~azure.storage.fileshare.ShareFileClient or
43        ~azure.storage.fileshare.ShareClient
44    :param str lease_id:
45        A string representing the lease ID of an existing lease. This value does not
46        need to be specified in order to acquire a new lease, or break one.
47    """
48    def __init__(
49            self, client, lease_id=None
50    ):  # pylint: disable=missing-client-constructor-parameter-credential,missing-client-constructor-parameter-kwargs
51        # type: (Union[ShareFileClient, ShareClient], Optional[str]) -> None
52        self.id = lease_id or str(uuid.uuid4())
53        self.last_modified = None
54        self.etag = None
55        if hasattr(client, 'file_name'):
56            self._client = client._client.file  # type: ignore # pylint: disable=protected-access
57            self._snapshot = None
58        elif hasattr(client, 'share_name'):
59            self._client = client._client.share
60            self._snapshot = client.snapshot
61        else:
62            raise TypeError("Lease must use ShareFileClient or ShareClient.")
63
64    def __enter__(self):
65        return self
66
67    def __exit__(self, *args):
68        self.release()
69
70    @distributed_trace
71    def acquire(self, **kwargs):
72        # type: (**Any) -> None
73        """Requests a new lease. This operation establishes and manages a lock on a
74        file or share for write and delete operations. If the file or share does not have an active lease,
75        the File or Share service creates a lease on the file or share. If the file has an active lease,
76        you can only request a new lease using the active lease ID.
77
78
79        If the file or share does not have an active lease, the File or Share service creates a
80        lease on the file and returns a new lease ID.
81
82        :keyword int lease_duration:
83            Specifies the duration of the lease, in seconds, or negative one
84            (-1) for a lease that never expires. File leases never expire. A non-infinite share lease can be
85            between 15 and 60 seconds. A share lease duration cannot be changed
86            using renew or change. Default is -1 (infinite share lease).
87
88        :keyword int timeout:
89            The timeout parameter is expressed in seconds.
90        :rtype: None
91        """
92        try:
93            lease_duration = kwargs.pop('lease_duration', -1)
94            if self._snapshot:
95                kwargs['sharesnapshot'] = self._snapshot
96            response = self._client.acquire_lease(
97                timeout=kwargs.pop('timeout', None),
98                duration=lease_duration,
99                proposed_lease_id=self.id,
100                cls=return_response_headers,
101                **kwargs)
102        except StorageErrorException as error:
103            process_storage_error(error)
104        self.id = response.get('lease_id')  # type: str
105        self.last_modified = response.get('last_modified')   # type: datetime
106        self.etag = response.get('etag')  # type: str
107
108    @distributed_trace
109    def renew(self, **kwargs):
110        # type: (Any) -> None
111        """Renews the share lease.
112
113        The share lease can be renewed if the lease ID specified in the
114        lease client matches that associated with the share. Note that
115        the lease may be renewed even if it has expired as long as the share
116        has not been leased again since the expiration of that lease. When you
117        renew a lease, the lease duration clock resets.
118
119        .. versionadded:: 12.6.0
120
121        :keyword int timeout:
122            The timeout parameter is expressed in seconds.
123        :return: None
124        """
125        if isinstance(self._client, FileOperations):
126            raise TypeError("Lease renewal operations are only valid for ShareClient.")
127        try:
128            response = self._client.renew_lease(
129                lease_id=self.id,
130                timeout=kwargs.pop('timeout', None),
131                sharesnapshot=self._snapshot,
132                cls=return_response_headers,
133                **kwargs)
134        except StorageErrorException as error:
135            process_storage_error(error)
136        self.etag = response.get('etag')  # type: str
137        self.id = response.get('lease_id')  # type: str
138        self.last_modified = response.get('last_modified')   # type: datetime
139
140    @distributed_trace
141    def release(self, **kwargs):
142        # type: (Any) -> None
143        """Releases the lease. The lease may be released if the lease ID specified on the request matches
144        that associated with the share or file. Releasing the lease allows another client to immediately acquire
145        the lease for the share or file as soon as the release is complete.
146
147        :keyword int timeout:
148            The timeout parameter is expressed in seconds.
149        :return: None
150        """
151        try:
152            if self._snapshot:
153                kwargs['sharesnapshot'] = self._snapshot
154            response = self._client.release_lease(
155                lease_id=self.id,
156                timeout=kwargs.pop('timeout', None),
157                cls=return_response_headers,
158                **kwargs)
159        except StorageErrorException as error:
160            process_storage_error(error)
161        self.etag = response.get('etag')  # type: str
162        self.id = response.get('lease_id')  # type: str
163        self.last_modified = response.get('last_modified')   # type: datetime
164
165    @distributed_trace
166    def change(self, proposed_lease_id, **kwargs):
167        # type: (str, Any) -> None
168        """ Changes the lease ID of an active lease. A change must include the current lease ID in x-ms-lease-id and
169        a new lease ID in x-ms-proposed-lease-id.
170
171        :param str proposed_lease_id:
172            Proposed lease ID, in a GUID string format. The File or Share service will raise an error
173            (Invalid request) if the proposed lease ID is not in the correct format.
174        :keyword int timeout:
175            The timeout parameter is expressed in seconds.
176        :return: None
177        """
178        try:
179            if self._snapshot:
180                kwargs['sharesnapshot'] = self._snapshot
181            response = self._client.change_lease(
182                lease_id=self.id,
183                proposed_lease_id=proposed_lease_id,
184                timeout=kwargs.pop('timeout', None),
185                cls=return_response_headers,
186                **kwargs)
187        except StorageErrorException as error:
188            process_storage_error(error)
189        self.etag = response.get('etag')  # type: str
190        self.id = response.get('lease_id')  # type: str
191        self.last_modified = response.get('last_modified')   # type: datetime
192
193    @distributed_trace
194    def break_lease(self, **kwargs):
195        # type: (Any) -> int
196        """Force breaks the lease if the file or share has an active lease. Any authorized request can break the lease;
197        the request is not required to specify a matching lease ID. An infinite lease breaks immediately.
198
199        Once a lease is broken, it cannot be changed. Any authorized request can break the lease;
200        the request is not required to specify a matching lease ID.
201        When a lease is successfully broken, the response indicates the interval
202        in seconds until a new lease can be acquired.
203
204        :keyword int lease_break_period:
205            This is the proposed duration of seconds that the share lease
206            should continue before it is broken, between 0 and 60 seconds. This
207            break period is only used if it is shorter than the time remaining
208            on the share lease. If longer, the time remaining on the share lease is used.
209            A new share lease will not be available before the break period has
210            expired, but the share lease may be held for longer than the break
211            period. If this header does not appear with a break
212            operation, a fixed-duration share lease breaks after the remaining share lease
213            period elapses, and an infinite share lease breaks immediately.
214
215            .. versionadded:: 12.6.0
216
217        :keyword int timeout:
218            The timeout parameter is expressed in seconds.
219        :return: Approximate time remaining in the lease period, in seconds.
220        :rtype: int
221        """
222        try:
223            lease_break_period = kwargs.pop('lease_break_period', None)
224            if self._snapshot:
225                kwargs['sharesnapshot'] = self._snapshot
226            if isinstance(self._client, ShareOperations):
227                kwargs['break_period'] = lease_break_period
228            if isinstance(self._client, FileOperations) and lease_break_period:
229                raise TypeError("Setting a lease break period is only applicable to Share leases.")
230
231            response = self._client.break_lease(
232                timeout=kwargs.pop('timeout', None),
233                cls=return_response_headers,
234                **kwargs)
235        except StorageErrorException as error:
236            process_storage_error(error)
237        return response.get('lease_time')  # type: ignore
238