p2api 1.1.8__tar.gz → 1.2.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.
- {p2api-1.1.8 → p2api-1.2.0}/PKG-INFO +40 -2
- {p2api-1.1.8 → p2api-1.2.0}/README.md +38 -0
- p2api-1.2.0/p2api/__about__.py +1 -0
- {p2api-1.1.8 → p2api-1.2.0}/p2api/p2api.py +3 -5
- {p2api-1.1.8 → p2api-1.2.0}/p2api/utils/generate_finding_charts.py +2 -1
- {p2api-1.1.8 → p2api-1.2.0}/p2api/utils/rrm_trigger.py +15 -9
- {p2api-1.1.8 → p2api-1.2.0}/p2api/utils/rrm_trigger_add_fc.py +76 -62
- {p2api-1.1.8 → p2api-1.2.0}/p2api/utils/too_trigger.py +1 -1
- p2api-1.1.8/p2api/__about__.py +0 -1
- {p2api-1.1.8 → p2api-1.2.0}/.gitignore +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/LICENSE +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/p2api/__init__.py +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/p2api/utils/__init__.py +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/p2api/utils/eso_period.py +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/p2api/utils/fits_time.py +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/p2api/utils/p2api_utils.py +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/p2api/utils/tz_utc.py +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/pyproject.toml +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/setup.cfg +0 -0
- {p2api-1.1.8 → p2api-1.2.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: p2api
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.0
|
|
4
4
|
Summary: Binding for the ESO phase 2 programmatic API
|
|
5
5
|
Project-URL: Homepage, https://www.eso.org/copdemo/apidoc/index.html
|
|
6
6
|
Author-email: Thomas Bierwirth <thomas.bierwirth@eso.org>, John Pritchard <john.pritchard@eso.org>
|
|
@@ -63,3 +63,41 @@ As of version 0.97.2: support for La Silla.
|
|
|
63
63
|
Please see the [ESO-USD p2api](<http://eso.org/sci/observing/phase2/p2intro/Phase2API.html>) page for more details.
|
|
64
64
|
|
|
65
65
|
Phase 2 programmatic API documentation can be found [here](<https://www.eso.org/copdemo/apidoc/>).
|
|
66
|
+
|
|
67
|
+
## Create a dedicated Virtual Environment for p2api
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
bash
|
|
71
|
+
python -m venv "${HOME}/envs/p2api"
|
|
72
|
+
. "${HOME}/envs/p2api/bin/activate"
|
|
73
|
+
python -m pip install --upgrade pip
|
|
74
|
+
python -m pip install p2api
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### To trigger an RRM observation
|
|
78
|
+
|
|
79
|
+
First activate the environment ( if it is not already activated):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
bash
|
|
83
|
+
. "${HOME}/envs/p2api/bin/activate"
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Next save your ESO User Portal credentials in the keyring if you want to be able to trigger without being prompted for the password each time:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
up_username=<ESO_User_Portal_username>
|
|
90
|
+
p2api_rrm_trigger -u ${up_username} -S
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Now you are ready to make a test trigger:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
up_username=<ESO_User_Portal_username>
|
|
97
|
+
p2api_rrm_trigger -u ${up_username} \
|
|
98
|
+
--env=production \
|
|
99
|
+
--obId=1234567 \
|
|
100
|
+
--account_id=12345 --RA=10:20:30.000 --DEC=-35:01:30.600 \
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
To make an actual trigger, add the command line argument ```--NOT_A_TEST```.
|
|
@@ -17,3 +17,41 @@ As of version 0.97.2: support for La Silla.
|
|
|
17
17
|
Please see the [ESO-USD p2api](<http://eso.org/sci/observing/phase2/p2intro/Phase2API.html>) page for more details.
|
|
18
18
|
|
|
19
19
|
Phase 2 programmatic API documentation can be found [here](<https://www.eso.org/copdemo/apidoc/>).
|
|
20
|
+
|
|
21
|
+
## Create a dedicated Virtual Environment for p2api
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bash
|
|
25
|
+
python -m venv "${HOME}/envs/p2api"
|
|
26
|
+
. "${HOME}/envs/p2api/bin/activate"
|
|
27
|
+
python -m pip install --upgrade pip
|
|
28
|
+
python -m pip install p2api
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### To trigger an RRM observation
|
|
32
|
+
|
|
33
|
+
First activate the environment ( if it is not already activated):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
bash
|
|
37
|
+
. "${HOME}/envs/p2api/bin/activate"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Next save your ESO User Portal credentials in the keyring if you want to be able to trigger without being prompted for the password each time:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
up_username=<ESO_User_Portal_username>
|
|
44
|
+
p2api_rrm_trigger -u ${up_username} -S
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Now you are ready to make a test trigger:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
up_username=<ESO_User_Portal_username>
|
|
51
|
+
p2api_rrm_trigger -u ${up_username} \
|
|
52
|
+
--env=production \
|
|
53
|
+
--obId=1234567 \
|
|
54
|
+
--account_id=12345 --RA=10:20:30.000 --DEC=-35:01:30.600 \
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
To make an actual trigger, add the command line argument ```--NOT_A_TEST```.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "1.2.0"
|
|
@@ -37,8 +37,8 @@ SOFTWARE.
|
|
|
37
37
|
from __future__ import print_function
|
|
38
38
|
import os
|
|
39
39
|
import json
|
|
40
|
-
import requests
|
|
41
40
|
import datetime
|
|
41
|
+
import requests
|
|
42
42
|
|
|
43
43
|
## The hatchling way...
|
|
44
44
|
try:
|
|
@@ -1742,7 +1742,7 @@ class ApiConnection(object):
|
|
|
1742
1742
|
files=None
|
|
1743
1743
|
_calibs=[]
|
|
1744
1744
|
if len(calibrations) > 0 :
|
|
1745
|
-
_calibs=calibrations.split(',')
|
|
1745
|
+
_calibs=[int(x) for x in calibrations.split(',')]
|
|
1746
1746
|
data={
|
|
1747
1747
|
"phone": phone,
|
|
1748
1748
|
"notify": notify.split(','),
|
|
@@ -2076,8 +2076,6 @@ Comments !536870979!:[$$Please add here any additional comment.$$]
|
|
|
2076
2076
|
|
|
2077
2077
|
rlist=[]
|
|
2078
2078
|
if finding_chart_files is not None :
|
|
2079
|
-
data['fc_previous_requests']={'finding_charts': [],}
|
|
2080
|
-
data['trigger_ticket_id']=trigger_ticket_id
|
|
2081
2079
|
|
|
2082
2080
|
for filename in finding_chart_files.split(',') :
|
|
2083
2081
|
if os.path.exists(filename) :
|
|
@@ -2088,7 +2086,7 @@ Comments !536870979!:[$$Please add here any additional comment.$$]
|
|
|
2088
2086
|
r=self.grrmapi_request(
|
|
2089
2087
|
'POST',
|
|
2090
2088
|
url=(
|
|
2091
|
-
'
|
|
2089
|
+
'rrm_trigger/%d/add_fc?' %(obId)
|
|
2092
2090
|
+'api_token=%s' %(self.access_token)
|
|
2093
2091
|
+'&url=%s/obsBlocks/%d' %(self.apiUrl,obId,)
|
|
2094
2092
|
+'&trigger_ticket_id=%d' %(trigger_ticket_id)
|
|
@@ -18,6 +18,7 @@ import requests
|
|
|
18
18
|
import p2api
|
|
19
19
|
from p2api.__about__ import __version__
|
|
20
20
|
import p2api.utils.p2api_utils as p2api_utils
|
|
21
|
+
|
|
21
22
|
################################################################################
|
|
22
23
|
logger=logging.getLogger()
|
|
23
24
|
################################################################################
|
|
@@ -226,7 +227,7 @@ def oneOB( api, obId ) :
|
|
|
226
227
|
def main():
|
|
227
228
|
|
|
228
229
|
################################################################################
|
|
229
|
-
# remove
|
|
230
|
+
# remove password from keyring...
|
|
230
231
|
if options.pwremove and options.user is not None :
|
|
231
232
|
keyring.delete_password('usd_p2api_interactive:www.eso.org', options.user)
|
|
232
233
|
sys.exit(0)
|
|
@@ -176,7 +176,10 @@ def oneOB( api, options, obId, overall_sdt ) :
|
|
|
176
176
|
)
|
|
177
177
|
return
|
|
178
178
|
|
|
179
|
-
|
|
179
|
+
triggerable_OB_statuses=['+','R']
|
|
180
|
+
if options.testing :
|
|
181
|
+
triggerable_OB_statuses+=['D',]
|
|
182
|
+
if OB['obStatus'] not in triggerable_OB_statuses :
|
|
180
183
|
logger.error(
|
|
181
184
|
'OB status is %s, OB not triggerable',
|
|
182
185
|
OB['obStatus']
|
|
@@ -230,16 +233,19 @@ def oneOB( api, options, obId, overall_sdt ) :
|
|
|
230
233
|
for _m in m.split('\n') :
|
|
231
234
|
logger.warning(_m)
|
|
232
235
|
#
|
|
236
|
+
#ccs_replies="\n".join(str(reply) for reply in (
|
|
237
|
+
# _r['replies'] if isinstance(_r['replies'], (list, tuple)) else [_r['replies']]
|
|
238
|
+
#))
|
|
233
239
|
# RRM trigger status
|
|
234
240
|
if 'rrm_trigger_status' in rj :
|
|
235
241
|
if rj['rrm_trigger_status'].lower() in ['failed','invalid',] :
|
|
236
242
|
logger.error(
|
|
237
|
-
"
|
|
243
|
+
"rrm_trigger_status = %s",
|
|
238
244
|
rj['rrm_trigger_status']
|
|
239
245
|
)
|
|
240
246
|
else :
|
|
241
247
|
logger.info(
|
|
242
|
-
"
|
|
248
|
+
"rrm_trigger_status = %s",
|
|
243
249
|
rj['rrm_trigger_status']
|
|
244
250
|
)
|
|
245
251
|
if rj.get('ccs_error',None) is not None :
|
|
@@ -249,9 +255,9 @@ def oneOB( api, options, obId, overall_sdt ) :
|
|
|
249
255
|
for m in rj['ccs_warnings'].split('\n') :
|
|
250
256
|
logger.warning(m)
|
|
251
257
|
if rj.get('ccs_replies',None) is not None :
|
|
252
|
-
for
|
|
258
|
+
for _m in rj['ccs_replies'] :
|
|
253
259
|
if len(_m) > 0 :
|
|
254
|
-
logger.info(_m)
|
|
260
|
+
logger.info("CCS_REPLY = %s",_m)
|
|
255
261
|
#
|
|
256
262
|
# DeskPro Ticket creation...
|
|
257
263
|
if (rj.get('trigger_ticket',{}) or {}).get('result',None) is not None :
|
|
@@ -274,7 +280,7 @@ def oneOB( api, options, obId, overall_sdt ) :
|
|
|
274
280
|
if len(_m) > 0 :
|
|
275
281
|
logger.warning(_m)
|
|
276
282
|
if rj.get('message',None) is not None :
|
|
277
|
-
for
|
|
283
|
+
for _m in rj['message'].split('\n') :
|
|
278
284
|
if len(_m) > 0 :
|
|
279
285
|
logger.info(_m)
|
|
280
286
|
if options.debug :
|
|
@@ -340,7 +346,7 @@ def main():
|
|
|
340
346
|
checked_runs=(options.runId or '').split(',')
|
|
341
347
|
if options.runId is None :
|
|
342
348
|
checked_runs="runs scheduled in P{cep}".format(cep=cep)
|
|
343
|
-
for run in runs :
|
|
349
|
+
for run in (runs or []) :
|
|
344
350
|
if (
|
|
345
351
|
( options.runId is None and run['scheduledPeriod'] == cep )
|
|
346
352
|
or
|
|
@@ -395,10 +401,10 @@ def main():
|
|
|
395
401
|
r=oneOB(api, options, obId, overall_sdt)
|
|
396
402
|
if r is not None :
|
|
397
403
|
rj=r[0].json()
|
|
398
|
-
if options.finding_charts is not None and 'id' in rj.get('trigger_ticket'
|
|
404
|
+
if options.finding_charts is not None and 'id' in (rj.get('trigger_ticket') or {}) :
|
|
399
405
|
options.ticket_id=rj['trigger_ticket']['id']
|
|
400
406
|
oneOB_add_fc(api, options, obId, overall_sdt)
|
|
401
|
-
|
|
407
|
+
edt=datetime.datetime.now(tz_utc)
|
|
402
408
|
logger.info(
|
|
403
409
|
'Total elapsed time to process RRM trigger %s...',
|
|
404
410
|
str(edt-overall_sdt)
|
|
@@ -32,7 +32,7 @@ class get_options:
|
|
|
32
32
|
parser.add_argument('--version', action='version', version='%(prog)s {version}'.format(version=__version__))
|
|
33
33
|
|
|
34
34
|
group = parser.add_argument_group("Required input")
|
|
35
|
-
group.add_argument("--ticket_id", dest="ticket_id", help="The ticket-ID [required]", default=None)
|
|
35
|
+
group.add_argument("--ticket_id", dest="ticket_id", help="The ticket-ID [required]", type=int, default=None)
|
|
36
36
|
group.add_argument("--finding_charts", dest="finding_charts", help="A comma separated list of finding chart files [required]", default=None)
|
|
37
37
|
group.add_argument("--obId", dest="obId", help="OB ID to trigger [required]", default=None)
|
|
38
38
|
group.add_argument("--account_id", dest="account_id", help="account_id [required]", type=int, default=None)
|
|
@@ -110,9 +110,9 @@ class get_options:
|
|
|
110
110
|
and not (
|
|
111
111
|
options.pwstore
|
|
112
112
|
or
|
|
113
|
-
options.
|
|
113
|
+
options.pwreenter
|
|
114
114
|
or
|
|
115
|
-
options.
|
|
115
|
+
options.pwremove
|
|
116
116
|
)
|
|
117
117
|
or not options.user
|
|
118
118
|
) :
|
|
@@ -128,6 +128,7 @@ class get_options:
|
|
|
128
128
|
self.DEC = options.DEC
|
|
129
129
|
|
|
130
130
|
self.OBname = options.OBname
|
|
131
|
+
self.runId = options.runId
|
|
131
132
|
|
|
132
133
|
self.old_fmt_ftp_file = options.old_fmt_ftp_file
|
|
133
134
|
|
|
@@ -179,8 +180,11 @@ def oneOB( api, options, obId, overall_sdt ) :
|
|
|
179
180
|
)
|
|
180
181
|
return
|
|
181
182
|
|
|
182
|
-
|
|
183
|
-
|
|
183
|
+
triggerable_OB_statuses=['+','R']
|
|
184
|
+
if options.testing :
|
|
185
|
+
triggerable_OB_statuses+=['D',]
|
|
186
|
+
if OB['obStatus'] not in triggerable_OB_statuses :
|
|
187
|
+
logger.error(
|
|
184
188
|
'OB status is %s, OB not triggerable',
|
|
185
189
|
OB['obStatus']
|
|
186
190
|
)
|
|
@@ -202,61 +206,71 @@ def oneOB( api, options, obId, overall_sdt ) :
|
|
|
202
206
|
"Adding %d %s to DeskPro ticket for RRM OBid = %d, OBname = '%s' @ %s...",
|
|
203
207
|
len(fcs_file_exists),FCorFCs,OB['obId'],OB['name'],OB['instrument']
|
|
204
208
|
)
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
for
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
for
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
for
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
for
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
json.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
209
|
+
if int(options.ticket_id) > 0 :
|
|
210
|
+
r = api.triggerRRM_add_fc(
|
|
211
|
+
trigger_ticket_id=options.ticket_id,
|
|
212
|
+
finding_chart_files=','.join(fcs_file_exists),
|
|
213
|
+
#
|
|
214
|
+
obId=obId,
|
|
215
|
+
account_id=options.account_id,
|
|
216
|
+
RA=options.RA,
|
|
217
|
+
DEC=options.DEC,
|
|
218
|
+
#
|
|
219
|
+
instrument=OB['instrument'],
|
|
220
|
+
OBname=OB['name'],
|
|
221
|
+
#
|
|
222
|
+
datetime_p2api_started=fits_utc(overall_sdt),
|
|
223
|
+
datetime_p2api_submitted=fits_utc(datetime.datetime.now(tz_utc)),
|
|
224
|
+
)
|
|
225
|
+
if r is not None :
|
|
226
|
+
if isinstance(r,list) :
|
|
227
|
+
i=0
|
|
228
|
+
for _r in r :
|
|
229
|
+
rj=_r.json()
|
|
230
|
+
if rj.get('error',None) is not None :
|
|
231
|
+
for m in rj['error'].split('\n') :
|
|
232
|
+
logger.error(m)
|
|
233
|
+
if rj.get('warning',None) is not None :
|
|
234
|
+
for m in rj['warning'].split('\n') :
|
|
235
|
+
logger.warning(m)
|
|
236
|
+
if rj.get('err_messages',None) is not None :
|
|
237
|
+
for m in rj['err_messages'] :
|
|
238
|
+
for _m in m.split('\n') :
|
|
239
|
+
logger.error(_m)
|
|
240
|
+
if rj.get('warn_messages',None) is not None :
|
|
241
|
+
for m in rj['warn_messages'] :
|
|
242
|
+
for _m in m.split('\n') :
|
|
243
|
+
logger.warning(_m)
|
|
244
|
+
if (rj.get('trigger_ticket',{}) or {}).get('err_messages',None) is not None :
|
|
245
|
+
for m in rj['trigger_ticket']['err_messages'] :
|
|
246
|
+
for _m in m.split('\n') :
|
|
247
|
+
logger.error(_m)
|
|
248
|
+
if (rj.get('trigger_ticket',{}) or {}).get('warn_messages',None) is not None :
|
|
249
|
+
for m in rj['trigger_ticket']['warn_messages'] :
|
|
250
|
+
for _m in m.split('\n') :
|
|
251
|
+
logger.warning(_m)
|
|
252
|
+
if rj.get('message',None) is not None :
|
|
253
|
+
for m in rj['message'].split('\n') :
|
|
254
|
+
logger.info(m)
|
|
255
|
+
if options.debug :
|
|
256
|
+
print(json.dumps(rj,indent=2,))
|
|
257
|
+
with open("/tmp/rrm_fc_{i}.json".format(i=i), 'w') as f :
|
|
258
|
+
json.dump(rj,f,indent=2,)
|
|
259
|
+
i+=1
|
|
260
|
+
oneOB_edt=datetime.datetime.now()
|
|
261
|
+
logger.info(
|
|
262
|
+
"%s added to DeskPro ticket for RRM OBid = %d, OBname = '%s' @ %s in %s.",
|
|
263
|
+
FCorFCs,obId, OB['name'], OB['instrument'], str(oneOB_edt-oneOB_sdt)
|
|
264
|
+
)
|
|
265
|
+
else :
|
|
266
|
+
logger.info(
|
|
267
|
+
"%s NOT added to DeskPro ticket [id=%d] for RRM OBid = %d, OBname = '%s' @ %s.",
|
|
268
|
+
FCorFCs,
|
|
269
|
+
options.ticket_id,
|
|
270
|
+
obId,
|
|
271
|
+
OB['name'],
|
|
272
|
+
OB['instrument'],
|
|
273
|
+
)
|
|
260
274
|
# ------------------------------------------------------------------------------
|
|
261
275
|
# ------------------------------------------------------------------------------
|
|
262
276
|
# ------------------------------------------------------------------------------
|
|
@@ -266,7 +280,7 @@ def main():
|
|
|
266
280
|
options = get_options()
|
|
267
281
|
overall_sdt=datetime.datetime.now(tz_utc)
|
|
268
282
|
################################################################################
|
|
269
|
-
# remove
|
|
283
|
+
# remove password from keyring...
|
|
270
284
|
if options.pwremove and options.user is not None :
|
|
271
285
|
keyring.delete_password(
|
|
272
286
|
'usd_p2api_interactive:www.eso.org',
|
|
@@ -302,7 +316,7 @@ def main():
|
|
|
302
316
|
checked_runs=(options.runId or '').split(',')
|
|
303
317
|
if options.runId is None :
|
|
304
318
|
checked_runs="runs scheduled in P{cep}".format(cep=cep)
|
|
305
|
-
for run in runs :
|
|
319
|
+
for run in (runs or []) :
|
|
306
320
|
if (
|
|
307
321
|
( options.runId is None and run['scheduledPeriod'] == cep )
|
|
308
322
|
or
|
|
@@ -149,7 +149,7 @@ def oneContainer( options, api, folderId, ) :
|
|
|
149
149
|
def main():
|
|
150
150
|
options = get_options()
|
|
151
151
|
################################################################################
|
|
152
|
-
# remove
|
|
152
|
+
# remove password from keyring...
|
|
153
153
|
if options.pwremove and options.user is not None :
|
|
154
154
|
keyring.delete_password(
|
|
155
155
|
'usd_p2api_interactive:www.eso.org',
|
p2api-1.1.8/p2api/__about__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "1.1.8"
|
|
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
|