miniopy-async 1.17__tar.gz → 1.18__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {miniopy-async-1.17 → miniopy-async-1.18}/PKG-INFO +2 -2
- {miniopy-async-1.17 → miniopy-async-1.18}/README.md +1 -1
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/__init__.py +1 -1
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/api.py +132 -5
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/commonconfig.py +48 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/helpers.py +35 -44
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/time.py +5 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async.egg-info/PKG-INFO +2 -2
- {miniopy-async-1.17 → miniopy-async-1.18}/LICENSE +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/credentials/__init__.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/credentials/credentials.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/credentials/providers.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/datatypes.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/deleteobjects.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/error.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/legalhold.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/lifecycleconfig.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/minioadmin.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/notificationconfig.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/objectlockconfig.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/progress.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/replicationconfig.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/retention.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/select.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/signer.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/sse.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/sseconfig.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/tagging.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/versioningconfig.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async/xml.py +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async.egg-info/SOURCES.txt +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async.egg-info/dependency_links.txt +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async.egg-info/requires.txt +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/miniopy_async.egg-info/top_level.txt +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/setup.cfg +0 -0
- {miniopy-async-1.17 → miniopy-async-1.18}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: miniopy-async
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18
|
|
4
4
|
Summary: Asynchronous MinIO Client SDK for Python
|
|
5
5
|
Home-page: https://github.com/hlf20010508/miniopy-async
|
|
6
6
|
Author: L-ING
|
|
@@ -87,7 +87,7 @@ pipenv install miniopy-async
|
|
|
87
87
|
|
|
88
88
|
Github Repository
|
|
89
89
|
```sh
|
|
90
|
-
pipenv install git+https://github.com/hlf20010508/miniopy-async.git
|
|
90
|
+
pipenv install "miniopy-async@ git+https://github.com/hlf20010508/miniopy-async.git"
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
<span id="example"></span>
|
|
@@ -60,7 +60,7 @@ pipenv install miniopy-async
|
|
|
60
60
|
|
|
61
61
|
Github Repository
|
|
62
62
|
```sh
|
|
63
|
-
pipenv install git+https://github.com/hlf20010508/miniopy-async.git
|
|
63
|
+
pipenv install "miniopy-async@ git+https://github.com/hlf20010508/miniopy-async.git"
|
|
64
64
|
```
|
|
65
65
|
|
|
66
66
|
<span id="example"></span>
|
|
@@ -33,6 +33,9 @@ import asyncio
|
|
|
33
33
|
import itertools
|
|
34
34
|
import os
|
|
35
35
|
import platform
|
|
36
|
+
from random import random
|
|
37
|
+
from io import BytesIO
|
|
38
|
+
import tarfile
|
|
36
39
|
|
|
37
40
|
# import weakref
|
|
38
41
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -44,7 +47,7 @@ import aiofile
|
|
|
44
47
|
import aiohttp
|
|
45
48
|
|
|
46
49
|
from . import __title__, __version__, time
|
|
47
|
-
from .commonconfig import COPY, ComposeSource, CopySource, REPLACE, Tags
|
|
50
|
+
from .commonconfig import COPY, ComposeSource, CopySource, REPLACE, Tags, SnowballObject
|
|
48
51
|
from .credentials import StaticProvider
|
|
49
52
|
from .datatypes import (
|
|
50
53
|
AsyncEventIterable,
|
|
@@ -376,7 +379,7 @@ class Minio: # pylint: disable=too-many-public-methods
|
|
|
376
379
|
query_params=query_params,
|
|
377
380
|
session=session,
|
|
378
381
|
)
|
|
379
|
-
|
|
382
|
+
return response
|
|
380
383
|
else:
|
|
381
384
|
response = await self._url_open(
|
|
382
385
|
method,
|
|
@@ -407,7 +410,7 @@ class Minio: # pylint: disable=too-many-public-methods
|
|
|
407
410
|
query_params=query_params,
|
|
408
411
|
session=session,
|
|
409
412
|
)
|
|
410
|
-
|
|
413
|
+
return response
|
|
411
414
|
else:
|
|
412
415
|
response = await self._url_open(
|
|
413
416
|
method,
|
|
@@ -3977,8 +3980,11 @@ class Minio: # pylint: disable=too-many-public-methods
|
|
|
3977
3980
|
days=10))
|
|
3978
3981
|
|
|
3979
3982
|
async def main():
|
|
3980
|
-
await client.set_object_retention(
|
|
3981
|
-
|
|
3983
|
+
await client.set_object_retention(
|
|
3984
|
+
"my-bucket",
|
|
3985
|
+
"my-object",
|
|
3986
|
+
config
|
|
3987
|
+
)
|
|
3982
3988
|
|
|
3983
3989
|
loop = asyncio.get_event_loop()
|
|
3984
3990
|
loop.run_until_complete(main())
|
|
@@ -4000,6 +4006,127 @@ class Minio: # pylint: disable=too-many-public-methods
|
|
|
4000
4006
|
query_params=query_params,
|
|
4001
4007
|
)
|
|
4002
4008
|
|
|
4009
|
+
async def upload_snowball_objects(
|
|
4010
|
+
self,
|
|
4011
|
+
bucket_name,
|
|
4012
|
+
object_list,
|
|
4013
|
+
metadata=None,
|
|
4014
|
+
sse=None,
|
|
4015
|
+
tags=None,
|
|
4016
|
+
retention=None,
|
|
4017
|
+
legal_hold=False,
|
|
4018
|
+
staging_filename=None,
|
|
4019
|
+
compression=False,
|
|
4020
|
+
):
|
|
4021
|
+
"""
|
|
4022
|
+
Uploads multiple objects in a single put call. It is done by creating
|
|
4023
|
+
intermediate TAR file optionally compressed which is uploaded to S3
|
|
4024
|
+
service.
|
|
4025
|
+
|
|
4026
|
+
:param bucket_name: Name of the bucket.
|
|
4027
|
+
:param object_list: An iterable containing
|
|
4028
|
+
:class:`SnowballObject <SnowballObject>` object.
|
|
4029
|
+
:param metadata: Any additional metadata to be uploaded along
|
|
4030
|
+
with your PUT request.
|
|
4031
|
+
:param sse: Server-side encryption.
|
|
4032
|
+
:param tags: :class:`Tags` for the object.
|
|
4033
|
+
:param retention: :class:`Retention` configuration object.
|
|
4034
|
+
:param legal_hold: Flag to set legal hold for the object.
|
|
4035
|
+
:param staging_filename: A staging filename to create intermediate tarball.
|
|
4036
|
+
:param compression: Flag to compress TAR ball.
|
|
4037
|
+
:return: :class:`ObjectWriteResult` object.
|
|
4038
|
+
|
|
4039
|
+
Example::
|
|
4040
|
+
from miniopy_async import Minio
|
|
4041
|
+
from miniopy_async.commonconfig import SnowballObject
|
|
4042
|
+
import io
|
|
4043
|
+
from datetime import datetime
|
|
4044
|
+
import asyncio
|
|
4045
|
+
|
|
4046
|
+
client = Minio(
|
|
4047
|
+
"play.min.io",
|
|
4048
|
+
access_key="Q3AM3UQ867SPQQA43P2F",
|
|
4049
|
+
secret_key="zuf+tfteSlswRu7BJ86wekitnifILbZam1KYY3TG",
|
|
4050
|
+
secure=True, # http for False, https for True
|
|
4051
|
+
)
|
|
4052
|
+
|
|
4053
|
+
async def main():
|
|
4054
|
+
result = await client.upload_snowball_objects(
|
|
4055
|
+
"my-bucket",
|
|
4056
|
+
[
|
|
4057
|
+
SnowballObject("my-object1", filename="filename"),
|
|
4058
|
+
SnowballObject(
|
|
4059
|
+
"my-object2", data=io.BytesIO(b"hello"), length=5,
|
|
4060
|
+
),
|
|
4061
|
+
SnowballObject(
|
|
4062
|
+
"my-object3", data=io.BytesIO(b"world"), length=5,
|
|
4063
|
+
mod_time=datetime.now(),
|
|
4064
|
+
),
|
|
4065
|
+
],
|
|
4066
|
+
)
|
|
4067
|
+
|
|
4068
|
+
loop = asyncio.get_event_loop()
|
|
4069
|
+
loop.run_until_complete(main())
|
|
4070
|
+
loop.close()
|
|
4071
|
+
"""
|
|
4072
|
+
check_bucket_name(bucket_name, s3_check=self._base_url.is_aws_host)
|
|
4073
|
+
|
|
4074
|
+
object_name = f"snowball.{random()}.tar"
|
|
4075
|
+
|
|
4076
|
+
# turn list like objects into an iterator.
|
|
4077
|
+
object_list = itertools.chain(object_list)
|
|
4078
|
+
|
|
4079
|
+
metadata = metadata or {}
|
|
4080
|
+
metadata["X-Amz-Meta-Snowball-Auto-Extract"] = "true"
|
|
4081
|
+
|
|
4082
|
+
name = staging_filename
|
|
4083
|
+
mode = "w:gz" if compression else "w"
|
|
4084
|
+
fileobj = None if name else BytesIO()
|
|
4085
|
+
with tarfile.open(name=name, mode=mode, fileobj=fileobj) as tar:
|
|
4086
|
+
for obj in object_list:
|
|
4087
|
+
if obj.filename:
|
|
4088
|
+
tar.add(obj.filename, obj.object_name)
|
|
4089
|
+
else:
|
|
4090
|
+
info = tarfile.TarInfo(obj.object_name)
|
|
4091
|
+
info.size = obj.length
|
|
4092
|
+
info.mtime = int(
|
|
4093
|
+
time.to_float(obj.mod_time or time.utcnow()),
|
|
4094
|
+
)
|
|
4095
|
+
tar.addfile(info, obj.data)
|
|
4096
|
+
|
|
4097
|
+
if not name:
|
|
4098
|
+
length = fileobj.tell()
|
|
4099
|
+
fileobj.seek(0)
|
|
4100
|
+
else:
|
|
4101
|
+
length = os.stat(name).st_size
|
|
4102
|
+
|
|
4103
|
+
part_size = 0 if length < MIN_PART_SIZE else length
|
|
4104
|
+
|
|
4105
|
+
if name:
|
|
4106
|
+
return await self.fput_object(
|
|
4107
|
+
bucket_name,
|
|
4108
|
+
object_name,
|
|
4109
|
+
staging_filename,
|
|
4110
|
+
metadata=metadata,
|
|
4111
|
+
sse=sse,
|
|
4112
|
+
tags=tags,
|
|
4113
|
+
retention=retention,
|
|
4114
|
+
legal_hold=legal_hold,
|
|
4115
|
+
part_size=part_size,
|
|
4116
|
+
)
|
|
4117
|
+
return await self.put_object(
|
|
4118
|
+
bucket_name,
|
|
4119
|
+
object_name,
|
|
4120
|
+
fileobj,
|
|
4121
|
+
length,
|
|
4122
|
+
metadata=metadata,
|
|
4123
|
+
sse=sse,
|
|
4124
|
+
tags=tags,
|
|
4125
|
+
retention=retention,
|
|
4126
|
+
legal_hold=legal_hold,
|
|
4127
|
+
part_size=part_size,
|
|
4128
|
+
)
|
|
4129
|
+
|
|
4003
4130
|
async def _list_objects(
|
|
4004
4131
|
# pylint: disable=too-many-arguments,too-many-branches
|
|
4005
4132
|
self,
|
|
@@ -508,3 +508,51 @@ class ComposeSource(ObjectConditionalReadArgs):
|
|
|
508
508
|
src.modified_since,
|
|
509
509
|
src.unmodified_since,
|
|
510
510
|
)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
class SnowballObject:
|
|
514
|
+
"""A source object definition for upload_snowball_objects method."""
|
|
515
|
+
|
|
516
|
+
def __init__(
|
|
517
|
+
self,
|
|
518
|
+
object_name,
|
|
519
|
+
filename=None,
|
|
520
|
+
data=None,
|
|
521
|
+
length=None,
|
|
522
|
+
mod_time=None,
|
|
523
|
+
):
|
|
524
|
+
self._object_name = object_name
|
|
525
|
+
if (filename is not None) ^ (data is not None):
|
|
526
|
+
self._filename = filename
|
|
527
|
+
self._data = data
|
|
528
|
+
self._length = length
|
|
529
|
+
else:
|
|
530
|
+
raise ValueError("only one of filename or data must be provided")
|
|
531
|
+
if mod_time is not None and not isinstance(mod_time, datetime.datetime):
|
|
532
|
+
raise ValueError("mod_time must be datetime.datetime type")
|
|
533
|
+
self._mod_time = mod_time
|
|
534
|
+
|
|
535
|
+
@property
|
|
536
|
+
def object_name(self):
|
|
537
|
+
"""Get object name."""
|
|
538
|
+
return self._object_name
|
|
539
|
+
|
|
540
|
+
@property
|
|
541
|
+
def filename(self):
|
|
542
|
+
"""Get filename."""
|
|
543
|
+
return self._filename
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def data(self):
|
|
547
|
+
"""Get data."""
|
|
548
|
+
return self._data
|
|
549
|
+
|
|
550
|
+
@property
|
|
551
|
+
def length(self):
|
|
552
|
+
"""Get length."""
|
|
553
|
+
return self._length
|
|
554
|
+
|
|
555
|
+
@property
|
|
556
|
+
def mod_time(self):
|
|
557
|
+
"""Get modification time."""
|
|
558
|
+
return self._mod_time
|
|
@@ -43,19 +43,16 @@ MAX_MULTIPART_OBJECT_SIZE = 5 * 1024 * 1024 * 1024 * 1024 # 5TiB
|
|
|
43
43
|
MAX_PART_SIZE = 5 * 1024 * 1024 * 1024 # 5GiB
|
|
44
44
|
MIN_PART_SIZE = 5 * 1024 * 1024 # 5MiB
|
|
45
45
|
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
_BUCKET_NAME_REGEX = re.compile(r'^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$')
|
|
47
|
+
_OLD_BUCKET_NAME_REGEX = re.compile(
|
|
48
|
+
r'^[a-z0-9][a-z0-9_\.\-\:]{1,61}[a-z0-9]$',
|
|
49
|
+
re.IGNORECASE
|
|
48
50
|
)
|
|
49
|
-
_VALID_BUCKETNAME_STRICT_REGEX = re.compile("^[a-z0-9][a-z0-9\\.\\-]{1,61}[a-z0-9]$")
|
|
50
|
-
_VALID_IP_ADDRESS = re.compile(r"^(\d+\.){3}\d+$")
|
|
51
|
-
_ALLOWED_HOSTNAME_REGEX = re.compile(
|
|
52
|
-
"^((?!-)(?!_)[A-Z_\\d-]{1,63}(?<!-)(?<!_)\\.)*((?!_)(?!-)"
|
|
53
|
-
+ "[A-Z_\\d-]{1,63}(?<!-)(?<!_))$",
|
|
54
|
-
re.IGNORECASE,
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
_EXTRACT_REGION_REGEX = re.compile("s3[.-]?(.+?).amazonaws.com")
|
|
58
51
|
|
|
52
|
+
_IPV4_REGEX = re.compile(
|
|
53
|
+
r'^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}'
|
|
54
|
+
r'(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$'
|
|
55
|
+
)
|
|
59
56
|
|
|
60
57
|
def quote(resource, safe="/", encoding=None, errors=None):
|
|
61
58
|
"""
|
|
@@ -191,44 +188,38 @@ def makedirs(path):
|
|
|
191
188
|
) from exc
|
|
192
189
|
|
|
193
190
|
|
|
194
|
-
def check_bucket_name(
|
|
191
|
+
def check_bucket_name(
|
|
192
|
+
bucket_name: str,
|
|
193
|
+
strict: bool = False,
|
|
194
|
+
s3_check: bool = False,
|
|
195
|
+
):
|
|
195
196
|
"""Check whether bucket name is valid optional with strict check or not."""
|
|
196
197
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if len(bucket_name) < 3:
|
|
204
|
-
raise ValueError("Bucket name cannot be less than" " 3 characters.")
|
|
205
|
-
if len(bucket_name) > 63:
|
|
206
|
-
raise ValueError("Bucket name cannot be greater than" " 63 characters.")
|
|
198
|
+
if strict:
|
|
199
|
+
if not _BUCKET_NAME_REGEX.match(bucket_name):
|
|
200
|
+
raise ValueError(f'invalid bucket name {bucket_name}')
|
|
201
|
+
else:
|
|
202
|
+
if not _OLD_BUCKET_NAME_REGEX.match(bucket_name):
|
|
203
|
+
raise ValueError(f'invalid bucket name {bucket_name}')
|
|
207
204
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
if _IPV4_REGEX.match(bucket_name):
|
|
206
|
+
raise ValueError(f'bucket name {bucket_name} must not be formatted '
|
|
207
|
+
'as an IP address')
|
|
211
208
|
|
|
212
|
-
unallowed_successive_chars = [
|
|
209
|
+
unallowed_successive_chars = ['..', '.-', '-.']
|
|
213
210
|
if any(x in bucket_name for x in unallowed_successive_chars):
|
|
214
|
-
raise ValueError(
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
match = _VALID_BUCKETNAME_REGEX.match(bucket_name)
|
|
227
|
-
if (not match) or match.end() != len(bucket_name):
|
|
228
|
-
raise ValueError(
|
|
229
|
-
"Bucket name does not follow S3 standards."
|
|
230
|
-
" Bucket: {0}".format(bucket_name)
|
|
231
|
-
)
|
|
211
|
+
raise ValueError(f'bucket name {bucket_name} contains invalid '
|
|
212
|
+
'successive characters')
|
|
213
|
+
|
|
214
|
+
if (
|
|
215
|
+
s3_check and
|
|
216
|
+
bucket_name.startswith("xn--") or
|
|
217
|
+
bucket_name.endswith("-s3alias") or
|
|
218
|
+
bucket_name.endswith("--ol-s3")
|
|
219
|
+
):
|
|
220
|
+
raise ValueError(f"bucket name {bucket_name} must not start with "
|
|
221
|
+
"'xn--' and must not end with '--s3alias' or "
|
|
222
|
+
"'--ol-s3'")
|
|
232
223
|
|
|
233
224
|
|
|
234
225
|
def check_non_empty_string(string):
|
|
@@ -22,6 +22,7 @@ from __future__ import absolute_import
|
|
|
22
22
|
|
|
23
23
|
import locale
|
|
24
24
|
from contextlib import contextmanager
|
|
25
|
+
import time as ctime
|
|
25
26
|
from datetime import datetime, timezone
|
|
26
27
|
|
|
27
28
|
from . import __LOCALE_LOCK__
|
|
@@ -96,3 +97,7 @@ def utcnow():
|
|
|
96
97
|
def to_signer_date(value):
|
|
97
98
|
"""Format datetime into SignatureV4 date formatted string."""
|
|
98
99
|
return _to_utc(value).strftime("%Y%m%d")
|
|
100
|
+
|
|
101
|
+
def to_float(value: datetime) -> float:
|
|
102
|
+
"""Convert datetime into float value."""
|
|
103
|
+
return ctime.mktime(value.timetuple()) + value.microsecond * 1e-6
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: miniopy-async
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.18
|
|
4
4
|
Summary: Asynchronous MinIO Client SDK for Python
|
|
5
5
|
Home-page: https://github.com/hlf20010508/miniopy-async
|
|
6
6
|
Author: L-ING
|
|
@@ -87,7 +87,7 @@ pipenv install miniopy-async
|
|
|
87
87
|
|
|
88
88
|
Github Repository
|
|
89
89
|
```sh
|
|
90
|
-
pipenv install git+https://github.com/hlf20010508/miniopy-async.git
|
|
90
|
+
pipenv install "miniopy-async@ git+https://github.com/hlf20010508/miniopy-async.git"
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
<span id="example"></span>
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|