cloudos-cli 2.40.0__tar.gz → 2.42.0__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.
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/PKG-INFO +21 -1
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/README.md +20 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/__main__.py +18 -8
- cloudos_cli-2.42.0/cloudos_cli/_version.py +1 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/datasets/datasets.py +24 -1
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/link/link.py +70 -18
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli.egg-info/PKG-INFO +21 -1
- cloudos_cli-2.40.0/cloudos_cli/_version.py +0 -1
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/LICENSE +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/__init__.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/clos.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/configure/__init__.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/configure/configure.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/datasets/__init__.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/import_wf/__init__.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/import_wf/import_wf.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/jobs/__init__.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/jobs/job.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/link/__init__.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/queue/__init__.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/queue/queue.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/utils/__init__.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/utils/array_job.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/utils/cloud.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/utils/details.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/utils/errors.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/utils/requests.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli/utils/resources.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli.egg-info/SOURCES.txt +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli.egg-info/dependency_links.txt +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli.egg-info/entry_points.txt +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli.egg-info/requires.txt +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/cloudos_cli.egg-info/top_level.txt +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/setup.cfg +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/setup.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/tests/__init__.py +0 -0
- {cloudos_cli-2.40.0 → cloudos_cli-2.42.0}/tests/functions_for_pytest.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudos_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.42.0
|
|
4
4
|
Summary: Python package for interacting with CloudOS
|
|
5
5
|
Home-page: https://github.com/lifebit-ai/cloudos-cli
|
|
6
6
|
Author: David Piñeyro
|
|
@@ -1116,6 +1116,26 @@ where profile `test` is used for all other necessary variables, but `--session-i
|
|
|
1116
1116
|
> [!NOTE]
|
|
1117
1117
|
> If running the CLI inside a jupyter session, the pre-configured CLI installation will have the session ID already installed and only the `--apikey` needs to be added.
|
|
1118
1118
|
|
|
1119
|
+
#### Linking a File Explorer folder to Interactive Analysis
|
|
1120
|
+
|
|
1121
|
+
Linking a File Explorer folder to an Interactive Analysis session can be done using the same subcommand as for the s3 folders:
|
|
1122
|
+
|
|
1123
|
+
```console
|
|
1124
|
+
cloudos datasets link <File Explorer FOLDER PATH> <options>
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
Examples (one per line):
|
|
1128
|
+
|
|
1129
|
+
```console
|
|
1130
|
+
cloudos datasets link Data/ingest_data --profile test
|
|
1131
|
+
cloudos datasets link Data/SEQUENCES --session-id 1234
|
|
1132
|
+
cloudos datasets link Data/SEQUENCES/HCC --session-id 1234
|
|
1133
|
+
cloudos datasets link "Analyses Results/HLA" --session-id 1234
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
> [!NOTE]
|
|
1137
|
+
> Virtual folders in File Explorer, the ones a user has created in File explorer and are not actual storage locations, cannot be linked.
|
|
1138
|
+
|
|
1119
1139
|
#### Create a (virtual) folder
|
|
1120
1140
|
|
|
1121
1141
|
New folders can be created within the `Data` dataset and its subfolders using the following command
|
|
@@ -1081,6 +1081,26 @@ where profile `test` is used for all other necessary variables, but `--session-i
|
|
|
1081
1081
|
> [!NOTE]
|
|
1082
1082
|
> If running the CLI inside a jupyter session, the pre-configured CLI installation will have the session ID already installed and only the `--apikey` needs to be added.
|
|
1083
1083
|
|
|
1084
|
+
#### Linking a File Explorer folder to Interactive Analysis
|
|
1085
|
+
|
|
1086
|
+
Linking a File Explorer folder to an Interactive Analysis session can be done using the same subcommand as for the s3 folders:
|
|
1087
|
+
|
|
1088
|
+
```console
|
|
1089
|
+
cloudos datasets link <File Explorer FOLDER PATH> <options>
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
Examples (one per line):
|
|
1093
|
+
|
|
1094
|
+
```console
|
|
1095
|
+
cloudos datasets link Data/ingest_data --profile test
|
|
1096
|
+
cloudos datasets link Data/SEQUENCES --session-id 1234
|
|
1097
|
+
cloudos datasets link Data/SEQUENCES/HCC --session-id 1234
|
|
1098
|
+
cloudos datasets link "Analyses Results/HLA" --session-id 1234
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
> [!NOTE]
|
|
1102
|
+
> Virtual folders in File Explorer, the ones a user has created in File explorer and are not actual storage locations, cannot be linked.
|
|
1103
|
+
|
|
1084
1104
|
#### Create a (virtual) folder
|
|
1085
1105
|
|
|
1086
1106
|
New folders can be created within the `Data` dataset and its subfolders using the following command
|
|
@@ -3415,24 +3415,27 @@ def rm_item(ctx, target_path, apikey, cloudos_url,
|
|
|
3415
3415
|
|
|
3416
3416
|
|
|
3417
3417
|
@datasets.command(name="link")
|
|
3418
|
-
@click.argument("
|
|
3418
|
+
@click.argument("path", required=True)
|
|
3419
3419
|
@click.option('-k', '--apikey', help='Your CloudOS API key', required=True)
|
|
3420
3420
|
@click.option('-c', '--cloudos-url',
|
|
3421
3421
|
help=(f'The CloudOS url you are trying to access to. Default={CLOUDOS_URL}.'),
|
|
3422
3422
|
default=CLOUDOS_URL)
|
|
3423
|
+
@click.option('--project-name',
|
|
3424
|
+
help='The name of a CloudOS project.',
|
|
3425
|
+
required=False)
|
|
3423
3426
|
@click.option('--workspace-id', help='The specific CloudOS workspace id.', required=True)
|
|
3424
3427
|
@click.option('--session-id', help='The specific CloudOS interactive session id.', required=True)
|
|
3425
3428
|
@click.option('--disable-ssl-verification', is_flag=True, help='Disable SSL certificate verification.')
|
|
3426
3429
|
@click.option('--ssl-cert', help='Path to your SSL certificate file.')
|
|
3427
3430
|
@click.option('--profile', help='Profile to use from the config file', default='default')
|
|
3428
3431
|
@click.pass_context
|
|
3429
|
-
def link(ctx,
|
|
3432
|
+
def link(ctx, path, apikey, cloudos_url, project_name, workspace_id, session_id, disable_ssl_verification, ssl_cert, profile):
|
|
3430
3433
|
"""
|
|
3431
|
-
Link a S3
|
|
3434
|
+
Link a folder (S3 or File Explorer) to an active interactive analysis.
|
|
3432
3435
|
|
|
3433
|
-
|
|
3436
|
+
PATH [path]: the full path to the S3 folder to link or relative to File Explorer.
|
|
3437
|
+
E.g.: 's3://bucket-name/folder/subfolder', 'Data/Downloads' or 'Data'.
|
|
3434
3438
|
"""
|
|
3435
|
-
print(link.__doc__ + '\n')
|
|
3436
3439
|
|
|
3437
3440
|
profile = profile or ctx.default_map['datasets']['link']['profile']
|
|
3438
3441
|
|
|
@@ -3455,7 +3458,8 @@ def link(ctx, s3_path, apikey, cloudos_url, workspace_id, session_id, disable_ss
|
|
|
3455
3458
|
apikey=apikey,
|
|
3456
3459
|
cloudos_url=cloudos_url,
|
|
3457
3460
|
workspace_id=workspace_id,
|
|
3458
|
-
session_id=session_id
|
|
3461
|
+
session_id=session_id,
|
|
3462
|
+
project_name=project_name
|
|
3459
3463
|
)
|
|
3460
3464
|
)
|
|
3461
3465
|
# Unpack the user options
|
|
@@ -3463,17 +3467,23 @@ def link(ctx, s3_path, apikey, cloudos_url, workspace_id, session_id, disable_ss
|
|
|
3463
3467
|
cloudos_url = user_options['cloudos_url']
|
|
3464
3468
|
workspace_id = user_options['workspace_id']
|
|
3465
3469
|
session_id = user_options['session_id']
|
|
3470
|
+
project_name = user_options['project_name']
|
|
3471
|
+
|
|
3472
|
+
if not path.startswith("s3://") and project_name is None:
|
|
3473
|
+
# for non-s3 paths we need the project, for S3 we don't
|
|
3474
|
+
raise click.UsageError("When using File Explorer paths '--project-name' needs to be defined")
|
|
3466
3475
|
|
|
3467
3476
|
verify_ssl = ssl_selector(disable_ssl_verification, ssl_cert)
|
|
3477
|
+
|
|
3468
3478
|
link_p = Link(
|
|
3469
3479
|
cloudos_url=cloudos_url,
|
|
3470
3480
|
apikey=apikey,
|
|
3471
3481
|
workspace_id=workspace_id,
|
|
3472
3482
|
cromwell_token=None,
|
|
3473
|
-
project_name=
|
|
3483
|
+
project_name=project_name,
|
|
3474
3484
|
verify=verify_ssl
|
|
3475
3485
|
)
|
|
3476
|
-
link_p.
|
|
3486
|
+
link_p.link_folder(path, session_id)
|
|
3477
3487
|
|
|
3478
3488
|
|
|
3479
3489
|
if __name__ == "__main__":
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '2.42.0'
|
|
@@ -489,7 +489,7 @@ class Datasets(Cloudos):
|
|
|
489
489
|
return response
|
|
490
490
|
|
|
491
491
|
def copy_item(self, item, destination_id, destination_kind):
|
|
492
|
-
"""Copy a file or folder (S3 or Virtual) to a destination in CloudOS."""
|
|
492
|
+
"""Copy a file or folder (S3, Azure or Virtual) to a destination in CloudOS."""
|
|
493
493
|
headers = {
|
|
494
494
|
"accept": "application/json",
|
|
495
495
|
"content-type": "application/json",
|
|
@@ -526,6 +526,29 @@ class Datasets(Cloudos):
|
|
|
526
526
|
"sizeInBytes": item.get("sizeInBytes", 0)
|
|
527
527
|
}
|
|
528
528
|
url = f"{self.cloudos_url}/api/v1/files/s3?teamId={self.workspace_id}"
|
|
529
|
+
# Azure folder
|
|
530
|
+
elif item.get("folderType") == "AzureBlobFolder":
|
|
531
|
+
payload = {
|
|
532
|
+
"blobContainerName": item["blobContainerName"],
|
|
533
|
+
"blobPrefix": item["blobPrefix"],
|
|
534
|
+
"blobStorageAccountName": item["blobStorageAccountName"],
|
|
535
|
+
"name": item["name"],
|
|
536
|
+
"parent": parent
|
|
537
|
+
}
|
|
538
|
+
url = f"{self.cloudos_url}/api/v1/folders/azure-blob?teamId={self.workspace_id}"
|
|
539
|
+
# Azure file
|
|
540
|
+
elif item.get("fileType") == "AzureBlobFile":
|
|
541
|
+
payload = {
|
|
542
|
+
"blobContainerName": item["blobContainerName"],
|
|
543
|
+
"blobName": item["blobName"],
|
|
544
|
+
"blobStorageAccountName": item["blobStorageAccountName"],
|
|
545
|
+
"name": item["name"],
|
|
546
|
+
"parent": parent,
|
|
547
|
+
"isManagedByLifebit": item.get("isManagedByLifebit", False),
|
|
548
|
+
"sizeInBytes": item.get("sizeInBytes", 0)
|
|
549
|
+
}
|
|
550
|
+
url = f"{self.cloudos_url}/api/v1/files/azure-blob?teamId={self.workspace_id}"
|
|
551
|
+
|
|
529
552
|
else:
|
|
530
553
|
raise ValueError(f"Unknown item type for copy: {item.get('name')}")
|
|
531
554
|
response = retry_requests_post(url, headers=headers, json=payload)
|
|
@@ -7,6 +7,8 @@ from typing import Union
|
|
|
7
7
|
from cloudos_cli.clos import Cloudos
|
|
8
8
|
from cloudos_cli.utils.requests import retry_requests_post
|
|
9
9
|
from urllib.parse import urlparse
|
|
10
|
+
from cloudos_cli.utils.array_job import extract_project, get_file_or_folder_id
|
|
11
|
+
import json
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@dataclass
|
|
@@ -30,24 +32,24 @@ class Link(Cloudos):
|
|
|
30
32
|
project_name: str
|
|
31
33
|
verify: Union[bool, str] = True
|
|
32
34
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
"""Link
|
|
35
|
+
def link_folder(self,
|
|
36
|
+
folder: str,
|
|
37
|
+
session_id: str) -> dict:
|
|
38
|
+
"""Link a folder (S3 or File Explorer) to an interactive session.
|
|
37
39
|
|
|
38
40
|
Parameters
|
|
39
41
|
----------
|
|
40
|
-
|
|
41
|
-
The
|
|
42
|
+
folder : str
|
|
43
|
+
The folder to link.
|
|
42
44
|
session_id : str
|
|
43
45
|
The interactive session ID.
|
|
44
46
|
|
|
45
47
|
Raises
|
|
46
48
|
------
|
|
47
49
|
ValueError
|
|
48
|
-
If the
|
|
50
|
+
If the URL already exists with 'mounted' status
|
|
49
51
|
If the API key is invalid or permissions are insufficient
|
|
50
|
-
If the
|
|
52
|
+
If the URL is invalid or the session is not active.
|
|
51
53
|
"""
|
|
52
54
|
url = (
|
|
53
55
|
f"{self.cloudos_url}/api/v1/"
|
|
@@ -58,22 +60,35 @@ class Link(Cloudos):
|
|
|
58
60
|
"Content-type": "application/json",
|
|
59
61
|
"apikey": self.apikey
|
|
60
62
|
}
|
|
61
|
-
|
|
63
|
+
# determine if is file explorer or s3
|
|
64
|
+
if folder.startswith('s3://'):
|
|
65
|
+
data = self.parse_s3_path(folder)
|
|
66
|
+
type_folder = "S3"
|
|
67
|
+
else:
|
|
68
|
+
data = self.parse_file_explorer_path(folder)
|
|
69
|
+
type_folder = "File Explorer"
|
|
62
70
|
r = retry_requests_post(url, headers=headers, json=data, verify=self.verify)
|
|
63
|
-
|
|
64
71
|
if r.status_code == 403:
|
|
65
|
-
raise ValueError(f"Provided folder already exists with 'mounted' status")
|
|
72
|
+
raise ValueError(f"Provided {type_folder} folder already exists with 'mounted' status")
|
|
66
73
|
elif r.status_code == 401:
|
|
67
74
|
raise ValueError(f"Forbidden: Invalid API key or insufficient permissions")
|
|
68
75
|
elif r.status_code == 400:
|
|
69
|
-
|
|
76
|
+
r_content = json.loads(r.content)
|
|
77
|
+
if r_content["message"] == "Invalid Supported DataItem folderType. Supported values are S3Folder":
|
|
78
|
+
raise ValueError(f"Invalid Supported DataItem '{type_folder}' folderType. Virtual folders cannot be linked.")
|
|
79
|
+
elif r_content["message"] == "Request failed with status code 403":
|
|
80
|
+
raise ValueError(f"Interactive Analysis session is not active")
|
|
81
|
+
else:
|
|
82
|
+
raise ValueError(f"Cannot link folder")
|
|
70
83
|
elif r.status_code == 204:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
84
|
+
if type_folder == "S3":
|
|
85
|
+
full_path = (
|
|
86
|
+
f"s3://{data['dataItem']['data']['s3BucketName']}/"
|
|
87
|
+
f"{data['dataItem']['data']['s3Prefix']}"
|
|
88
|
+
)
|
|
89
|
+
else:
|
|
90
|
+
full_path = folder
|
|
91
|
+
print(f"Succesfully linked {type_folder} folder: {full_path}")
|
|
77
92
|
|
|
78
93
|
def parse_s3_path(self, s3_url):
|
|
79
94
|
"""
|
|
@@ -123,3 +138,40 @@ class Link(Cloudos):
|
|
|
123
138
|
}
|
|
124
139
|
}
|
|
125
140
|
|
|
141
|
+
def parse_file_explorer_path(self, path):
|
|
142
|
+
"""
|
|
143
|
+
Parses a file path and returns the base name and full path.
|
|
144
|
+
|
|
145
|
+
Parameters
|
|
146
|
+
----------
|
|
147
|
+
file_path : str
|
|
148
|
+
The file path to parse.
|
|
149
|
+
|
|
150
|
+
Returns
|
|
151
|
+
-------
|
|
152
|
+
dict: A dictionary containing the parsed file information structured as:
|
|
153
|
+
"dataItem": {
|
|
154
|
+
"type": "File",
|
|
155
|
+
"data": {
|
|
156
|
+
"name": str, # The base name of the file.
|
|
157
|
+
"fullPath": str # The full path of the file.
|
|
158
|
+
"""
|
|
159
|
+
# get folder id
|
|
160
|
+
folder_id = get_file_or_folder_id(
|
|
161
|
+
self.cloudos_url,
|
|
162
|
+
self.apikey,
|
|
163
|
+
self.workspace_id,
|
|
164
|
+
self.project_name,
|
|
165
|
+
self.verify,
|
|
166
|
+
path.strip("/"),
|
|
167
|
+
"",
|
|
168
|
+
is_file=False
|
|
169
|
+
)
|
|
170
|
+
parts = path.strip("/").split("/")
|
|
171
|
+
return {
|
|
172
|
+
"dataItem": {
|
|
173
|
+
"kind": "Folder",
|
|
174
|
+
"item": f"{folder_id}",
|
|
175
|
+
"name": f"{parts[-1]}"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudos_cli
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.42.0
|
|
4
4
|
Summary: Python package for interacting with CloudOS
|
|
5
5
|
Home-page: https://github.com/lifebit-ai/cloudos-cli
|
|
6
6
|
Author: David Piñeyro
|
|
@@ -1116,6 +1116,26 @@ where profile `test` is used for all other necessary variables, but `--session-i
|
|
|
1116
1116
|
> [!NOTE]
|
|
1117
1117
|
> If running the CLI inside a jupyter session, the pre-configured CLI installation will have the session ID already installed and only the `--apikey` needs to be added.
|
|
1118
1118
|
|
|
1119
|
+
#### Linking a File Explorer folder to Interactive Analysis
|
|
1120
|
+
|
|
1121
|
+
Linking a File Explorer folder to an Interactive Analysis session can be done using the same subcommand as for the s3 folders:
|
|
1122
|
+
|
|
1123
|
+
```console
|
|
1124
|
+
cloudos datasets link <File Explorer FOLDER PATH> <options>
|
|
1125
|
+
```
|
|
1126
|
+
|
|
1127
|
+
Examples (one per line):
|
|
1128
|
+
|
|
1129
|
+
```console
|
|
1130
|
+
cloudos datasets link Data/ingest_data --profile test
|
|
1131
|
+
cloudos datasets link Data/SEQUENCES --session-id 1234
|
|
1132
|
+
cloudos datasets link Data/SEQUENCES/HCC --session-id 1234
|
|
1133
|
+
cloudos datasets link "Analyses Results/HLA" --session-id 1234
|
|
1134
|
+
```
|
|
1135
|
+
|
|
1136
|
+
> [!NOTE]
|
|
1137
|
+
> Virtual folders in File Explorer, the ones a user has created in File explorer and are not actual storage locations, cannot be linked.
|
|
1138
|
+
|
|
1119
1139
|
#### Create a (virtual) folder
|
|
1120
1140
|
|
|
1121
1141
|
New folders can be created within the `Data` dataset and its subfolders using the following command
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '2.40.0'
|
|
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
|
|
File without changes
|