xfc-client 1.1.2__py3-none-any.whl
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.
- xfc_client/__init__.py +1 -0
- xfc_client/xfc.py +420 -0
- xfc_client-1.1.2.dist-info/METADATA +26 -0
- xfc_client-1.1.2.dist-info/RECORD +8 -0
- xfc_client-1.1.2.dist-info/WHEEL +5 -0
- xfc_client-1.1.2.dist-info/entry_points.txt +2 -0
- xfc_client-1.1.2.dist-info/licenses/LICENSE +26 -0
- xfc_client-1.1.2.dist-info/top_level.txt +1 -0
xfc_client/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "1.1.2"
|
xfc_client/xfc.py
ADDED
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
#! /usr/bin/env python
|
|
2
|
+
"""Command line tool for interacting with the JASMIN transfer cache (XFC) for users who are
|
|
3
|
+
logged into JASMIN and have full JASMIN accounts."""
|
|
4
|
+
|
|
5
|
+
# Author : Neil R Massey
|
|
6
|
+
# Date : 12/05/2017
|
|
7
|
+
|
|
8
|
+
import sys, os
|
|
9
|
+
import argparse
|
|
10
|
+
import requests
|
|
11
|
+
import json
|
|
12
|
+
|
|
13
|
+
from math import log
|
|
14
|
+
from xfc_client import VERSION
|
|
15
|
+
|
|
16
|
+
# switch off warnings
|
|
17
|
+
from requests.packages.urllib3.exceptions import InsecureRequestWarning
|
|
18
|
+
|
|
19
|
+
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class settings:
|
|
23
|
+
"""Settings for the xfc command line tool."""
|
|
24
|
+
|
|
25
|
+
# location of the xfc_control server / app
|
|
26
|
+
XFC_SERVER_URL = "https://xfc.jasmin.ac.uk/xfc_control"
|
|
27
|
+
XFC_API_URL = XFC_SERVER_URL + "/api/v1.1/"
|
|
28
|
+
try:
|
|
29
|
+
USER = os.environ["XFC_USER"]
|
|
30
|
+
except KeyError:
|
|
31
|
+
USER = os.environ["USER"]
|
|
32
|
+
VERSION = VERSION # version of this software
|
|
33
|
+
VERIFY = False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
unit_list = list(
|
|
37
|
+
zip(["bytes", "kB", "MB", "GB", "TB", "PB", "EB"], [0, 0, 1, 1, 1, 1, 1])
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def sizeof_fmt(num):
|
|
42
|
+
"""Human friendly file size"""
|
|
43
|
+
if num > 1:
|
|
44
|
+
exponent = min(int(log(num, 1024)), len(unit_list) - 1)
|
|
45
|
+
quotient = float(num) / 1024**exponent
|
|
46
|
+
unit, num_decimals = unit_list[exponent]
|
|
47
|
+
format_string = "{:>5.%sf} {}" % (num_decimals)
|
|
48
|
+
return format_string.format(quotient, unit)
|
|
49
|
+
elif num == 1:
|
|
50
|
+
return " 1 byte"
|
|
51
|
+
else:
|
|
52
|
+
return " 0 bytes"
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class bcolors:
|
|
56
|
+
MAGENTA = "\033[95m"
|
|
57
|
+
BLUE = "\033[94m"
|
|
58
|
+
GREEN = "\033[92m"
|
|
59
|
+
YELLOW = "\033[93m"
|
|
60
|
+
RED = "\033[91m"
|
|
61
|
+
BOLD = "\033[1m"
|
|
62
|
+
UNDERLINE = "\033[4m"
|
|
63
|
+
INVERT = "\033[7m"
|
|
64
|
+
ENDC = "\033[0m"
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def user_not_initialized_message():
|
|
68
|
+
sys.stdout.write(
|
|
69
|
+
bcolors.RED
|
|
70
|
+
+ "** ERROR ** - User "
|
|
71
|
+
+ settings.USER
|
|
72
|
+
+ " not initialised yet."
|
|
73
|
+
+ bcolors.ENDC
|
|
74
|
+
+ " Run "
|
|
75
|
+
+ bcolors.YELLOW
|
|
76
|
+
+ "xfc init"
|
|
77
|
+
+ bcolors.ENDC
|
|
78
|
+
+ " first.\n"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def print_response_error(response):
|
|
83
|
+
"""Print a concise summary of the error, rather than a whole output of html"""
|
|
84
|
+
for il in response.content.split("\n"):
|
|
85
|
+
if "Exception" in il:
|
|
86
|
+
print(il)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def error_from_response(response):
|
|
90
|
+
try:
|
|
91
|
+
data = response.json()
|
|
92
|
+
if "error" in data:
|
|
93
|
+
sys.stdout.write(
|
|
94
|
+
bcolors.RED + "** ERROR ** - " + data["error"] + bcolors.ENDC + "\n"
|
|
95
|
+
)
|
|
96
|
+
except:
|
|
97
|
+
sys.stdout.write(
|
|
98
|
+
bcolors.RED
|
|
99
|
+
+ "** ERROR ** "
|
|
100
|
+
+ str(response.status_code)
|
|
101
|
+
+ bcolors.ENDC
|
|
102
|
+
+ "\n"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def do_init(email=""):
|
|
107
|
+
"""Send the HTTP request (POST) to initialize a user's cache space."""
|
|
108
|
+
url = settings.XFC_API_URL + "user"
|
|
109
|
+
data = {"name": settings.USER}
|
|
110
|
+
if email != "":
|
|
111
|
+
data["email"] = email
|
|
112
|
+
|
|
113
|
+
response = requests.post(url, data=json.dumps(data), verify=settings.VERIFY)
|
|
114
|
+
# check the response code
|
|
115
|
+
if response.status_code == 200:
|
|
116
|
+
data = response.json()
|
|
117
|
+
sys.stdout.write(
|
|
118
|
+
bcolors.GREEN
|
|
119
|
+
+ "** SUCCESS ** - user initialised with:\n"
|
|
120
|
+
+ bcolors.ENDC
|
|
121
|
+
+ " Username : "
|
|
122
|
+
+ data["name"]
|
|
123
|
+
+ "\n"
|
|
124
|
+
+ " Email : "
|
|
125
|
+
+ data["email"]
|
|
126
|
+
+ "\n"
|
|
127
|
+
+ " Temporal Quota (TQ) : "
|
|
128
|
+
+ sizeof_fmt(data["quota_size"])
|
|
129
|
+
+ " days\n"
|
|
130
|
+
+ " Hard Quota (HQ) : "
|
|
131
|
+
+ sizeof_fmt(data["hard_limit_size"])
|
|
132
|
+
+ "\n"
|
|
133
|
+
+ " Path : "
|
|
134
|
+
+ data["cache_path"]
|
|
135
|
+
+ "\n"
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
sys.stdout.write(
|
|
139
|
+
bcolors.RED
|
|
140
|
+
+ "** ERROR ** - cannot initialise user "
|
|
141
|
+
+ settings.USER
|
|
142
|
+
+ bcolors.ENDC
|
|
143
|
+
+ "\n"
|
|
144
|
+
)
|
|
145
|
+
error_from_response(response)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def do_email(email=""):
|
|
149
|
+
"""Update the email address of the user by sending a PUT request if an email is
|
|
150
|
+
given, or return the email if no email supplied."""
|
|
151
|
+
url = settings.XFC_API_URL + "user?name=" + settings.USER
|
|
152
|
+
if email != "":
|
|
153
|
+
data = {"name": settings.USER, "email": email}
|
|
154
|
+
response = requests.put(url, data=json.dumps(data), verify=settings.VERIFY)
|
|
155
|
+
if response.status_code == 200:
|
|
156
|
+
data = response.json()
|
|
157
|
+
sys.stdout.write(
|
|
158
|
+
bcolors.GREEN
|
|
159
|
+
+ "** SUCCESS ** - user email updated to: "
|
|
160
|
+
+ data["email"]
|
|
161
|
+
+ bcolors.ENDC
|
|
162
|
+
+ "\n"
|
|
163
|
+
)
|
|
164
|
+
elif response.status_code == 404:
|
|
165
|
+
user_not_initialized_message()
|
|
166
|
+
else:
|
|
167
|
+
error_from_response(response)
|
|
168
|
+
else:
|
|
169
|
+
data = {"name": settings.USER}
|
|
170
|
+
response = requests.get(url, data=json.dumps(data), verify=settings.VERIFY)
|
|
171
|
+
if response.status_code == 200:
|
|
172
|
+
data = response.json()
|
|
173
|
+
sys.stdout.write(data["email"] + "\n")
|
|
174
|
+
elif response.status_code == 404:
|
|
175
|
+
user_not_initialized_message()
|
|
176
|
+
else:
|
|
177
|
+
error_from_response(response)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def do_info():
|
|
181
|
+
"""(re)print the info you get on initialising a user"""
|
|
182
|
+
url = settings.XFC_API_URL + "user?name=" + settings.USER
|
|
183
|
+
response = requests.get(url, verify=settings.VERIFY)
|
|
184
|
+
if response.status_code == 200:
|
|
185
|
+
data = response.json()
|
|
186
|
+
sys.stdout.write(
|
|
187
|
+
bcolors.GREEN
|
|
188
|
+
+ "** SUCCESS ** - user info:\n"
|
|
189
|
+
+ bcolors.ENDC
|
|
190
|
+
+ " Username : "
|
|
191
|
+
+ data["name"]
|
|
192
|
+
+ "\n"
|
|
193
|
+
+ " Email : "
|
|
194
|
+
+ data["email"]
|
|
195
|
+
+ "\n"
|
|
196
|
+
+ " Temporal Quota (TQ) : "
|
|
197
|
+
+ sizeof_fmt(data["quota_size"])
|
|
198
|
+
+ " days\n"
|
|
199
|
+
+ " Hard Quota (HQ) : "
|
|
200
|
+
+ sizeof_fmt(data["hard_limit_size"])
|
|
201
|
+
+ "\n"
|
|
202
|
+
+ " Path : "
|
|
203
|
+
+ data["cache_path"]
|
|
204
|
+
+ "\n"
|
|
205
|
+
)
|
|
206
|
+
elif response.status_code == 404:
|
|
207
|
+
user_not_initialized_message()
|
|
208
|
+
else:
|
|
209
|
+
error_from_response(response)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def do_path():
|
|
213
|
+
"""Send the HTTP request (GET) and process to get the path to the user space on the
|
|
214
|
+
cache."""
|
|
215
|
+
url = settings.XFC_API_URL + "user?name=" + settings.USER
|
|
216
|
+
response = requests.get(url, verify=settings.VERIFY)
|
|
217
|
+
if response.status_code == 200:
|
|
218
|
+
data = response.json()
|
|
219
|
+
sys.stdout.write(data["cache_path"] + "\n")
|
|
220
|
+
elif response.status_code == 404:
|
|
221
|
+
user_not_initialized_message()
|
|
222
|
+
else:
|
|
223
|
+
error_from_response(response)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def do_quota():
|
|
227
|
+
"""Send the HTTP request (GET) and process to get the remaining quota and total
|
|
228
|
+
quota for the user."""
|
|
229
|
+
url = settings.XFC_API_URL + "user?name=" + settings.USER
|
|
230
|
+
response = requests.get(url, verify=settings.VERIFY)
|
|
231
|
+
if response.status_code == 200:
|
|
232
|
+
data = response.json()
|
|
233
|
+
used = data["quota_used"]
|
|
234
|
+
allocated = data["quota_size"]
|
|
235
|
+
total = data["total_used"]
|
|
236
|
+
hard_limit = data["hard_limit_size"]
|
|
237
|
+
sys.stdout.write(
|
|
238
|
+
bcolors.MAGENTA
|
|
239
|
+
+ "-----------------------------\n"
|
|
240
|
+
+ "Quota for user: "
|
|
241
|
+
+ settings.USER
|
|
242
|
+
+ "\n"
|
|
243
|
+
+ "-----------------------------\n"
|
|
244
|
+
+ bcolors.ENDC
|
|
245
|
+
+ " Temporal Quota (TQ)\n"
|
|
246
|
+
" Used : "
|
|
247
|
+
+ sizeof_fmt(used)
|
|
248
|
+
+ " days\n"
|
|
249
|
+
+ " Allocated : "
|
|
250
|
+
+ sizeof_fmt(allocated)
|
|
251
|
+
+ " days\n"
|
|
252
|
+
)
|
|
253
|
+
if allocated - used < 0:
|
|
254
|
+
sys.stdout.write(bcolors.RED)
|
|
255
|
+
else:
|
|
256
|
+
sys.stdout.write(bcolors.GREEN)
|
|
257
|
+
sys.stdout.write(
|
|
258
|
+
" Remaining : " + sizeof_fmt(allocated - used) + " days\n" + bcolors.ENDC
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
sys.stdout.write("-----------------------------\n")
|
|
262
|
+
sys.stdout.write(" Hard Quota (HQ)\n")
|
|
263
|
+
sys.stdout.write(" Used : " + sizeof_fmt(total) + "\n")
|
|
264
|
+
sys.stdout.write(" Allocated : " + sizeof_fmt(hard_limit) + "\n")
|
|
265
|
+
if hard_limit - total < 0:
|
|
266
|
+
sys.stdout.write(bcolors.RED)
|
|
267
|
+
else:
|
|
268
|
+
sys.stdout.write(bcolors.GREEN)
|
|
269
|
+
sys.stdout.write(
|
|
270
|
+
" Remaining : " + sizeof_fmt(hard_limit - total) + bcolors.ENDC + "\n"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
elif response.status_code == 404:
|
|
274
|
+
user_not_initialized_message()
|
|
275
|
+
else:
|
|
276
|
+
error_from_response(response)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def do_notify():
|
|
280
|
+
"""Send the HTTP request (PUT) to switch on / off notifications for the user."""
|
|
281
|
+
# first get the status of notifications
|
|
282
|
+
url = settings.XFC_API_URL + "user?name=" + settings.USER
|
|
283
|
+
response = requests.get(url, verify=settings.VERIFY)
|
|
284
|
+
if response.status_code == 200:
|
|
285
|
+
data = response.json()
|
|
286
|
+
notify = data["notify"]
|
|
287
|
+
# update to inverse
|
|
288
|
+
put_data = {"name": settings.USER, "notify": not notify}
|
|
289
|
+
response = requests.put(url, data=json.dumps(put_data), verify=settings.VERIFY)
|
|
290
|
+
if response.status_code == 200:
|
|
291
|
+
data = response.json()
|
|
292
|
+
sys.stdout.write(
|
|
293
|
+
bcolors.GREEN
|
|
294
|
+
+ "** SUCCESS ** - user notifications updated to: "
|
|
295
|
+
+ ["off", "on"][put_data["notify"]]
|
|
296
|
+
+ bcolors.ENDC
|
|
297
|
+
+ "\n"
|
|
298
|
+
)
|
|
299
|
+
else:
|
|
300
|
+
error_from_response(response)
|
|
301
|
+
|
|
302
|
+
elif response.status_code == 404:
|
|
303
|
+
user_not_initialized_message()
|
|
304
|
+
else:
|
|
305
|
+
error_from_response(response)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def do_predict():
|
|
309
|
+
"""Send the HTTP request to the service which predicts when the user will exceed
|
|
310
|
+
their quota"""
|
|
311
|
+
url = settings.XFC_API_URL + "predict?name=" + settings.USER
|
|
312
|
+
response = requests.get(url, verify=settings.VERIFY)
|
|
313
|
+
if response.status_code == 200:
|
|
314
|
+
data = response.json()
|
|
315
|
+
name = data["name"]
|
|
316
|
+
over_quota = data["over_quota"]
|
|
317
|
+
path = data["cache_disk"]
|
|
318
|
+
time_predict = data["time_predict"]
|
|
319
|
+
pad = "".join(["-" for x in name])
|
|
320
|
+
|
|
321
|
+
sys.stdout.write(
|
|
322
|
+
bcolors.MAGENTA
|
|
323
|
+
+ "------------------------------------"
|
|
324
|
+
+ pad
|
|
325
|
+
+ "\n"
|
|
326
|
+
+ "Quota exceeded prediction for user: "
|
|
327
|
+
+ name
|
|
328
|
+
+ "\n"
|
|
329
|
+
+ "------------------------------------"
|
|
330
|
+
+ pad
|
|
331
|
+
+ "\n"
|
|
332
|
+
+ bcolors.ENDC
|
|
333
|
+
)
|
|
334
|
+
if over_quota > 0:
|
|
335
|
+
sys.stdout.write(bcolors.RED)
|
|
336
|
+
sys.stdout.write(
|
|
337
|
+
" Over quota by : " + sizeof_fmt(over_quota) + " days\n"
|
|
338
|
+
)
|
|
339
|
+
else:
|
|
340
|
+
sys.stdout.write(bcolors.GREEN)
|
|
341
|
+
sys.stdout.write(" Quota will not be exceeded\n")
|
|
342
|
+
if time_predict is not None:
|
|
343
|
+
sys.stdout.write(" On date : " + time_predict + "\n")
|
|
344
|
+
sys.stdout.write(bcolors.ENDC)
|
|
345
|
+
|
|
346
|
+
elif response.status_code == 404:
|
|
347
|
+
user_not_initialized_message()
|
|
348
|
+
else:
|
|
349
|
+
error_from_response(response)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def main():
|
|
353
|
+
# help string for the command parsing
|
|
354
|
+
command_help = (
|
|
355
|
+
"Available commands are : \n"
|
|
356
|
+
+ "init : Initialize the transfer cache for your JASMIN login\n"
|
|
357
|
+
+ "email : view, set or update email address\n"
|
|
358
|
+
+ "info : Get the user info\n"
|
|
359
|
+
+ "notify : Switch on / off email notifications of scheduled deletions (default is off)\n"
|
|
360
|
+
+ "path : Get the path to your storage area in the transfer cache\n"
|
|
361
|
+
+ "quota : Get the remaining free space in your quota\n"
|
|
362
|
+
+ "predict : Predict when the quota will be exceeded based on the current files\n"
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
parser = argparse.ArgumentParser(
|
|
366
|
+
prog="XFC",
|
|
367
|
+
formatter_class=argparse.RawTextHelpFormatter,
|
|
368
|
+
description="JASMIN transfer cache (XFC) command line tool",
|
|
369
|
+
)
|
|
370
|
+
parser.add_argument(
|
|
371
|
+
"--version", action="version", version="%(prog)s " + settings.VERSION
|
|
372
|
+
)
|
|
373
|
+
parser.add_argument(
|
|
374
|
+
"cmd",
|
|
375
|
+
choices=[
|
|
376
|
+
"init",
|
|
377
|
+
"path",
|
|
378
|
+
"email",
|
|
379
|
+
"info",
|
|
380
|
+
"quota",
|
|
381
|
+
"notify",
|
|
382
|
+
"predict",
|
|
383
|
+
"version",
|
|
384
|
+
],
|
|
385
|
+
help=command_help,
|
|
386
|
+
metavar="command",
|
|
387
|
+
)
|
|
388
|
+
parser.add_argument(
|
|
389
|
+
"--email",
|
|
390
|
+
action="store",
|
|
391
|
+
default="",
|
|
392
|
+
help="Email address for user in the init and email commands.",
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
args = parser.parse_args()
|
|
396
|
+
|
|
397
|
+
if args.email:
|
|
398
|
+
email = args.email
|
|
399
|
+
else:
|
|
400
|
+
email = ""
|
|
401
|
+
|
|
402
|
+
# switch on the commands
|
|
403
|
+
if args.cmd == "init":
|
|
404
|
+
do_init(email)
|
|
405
|
+
elif args.cmd == "email":
|
|
406
|
+
do_email(email)
|
|
407
|
+
elif args.cmd == "info":
|
|
408
|
+
do_info()
|
|
409
|
+
elif args.cmd == "path":
|
|
410
|
+
do_path()
|
|
411
|
+
elif args.cmd == "quota":
|
|
412
|
+
do_quota()
|
|
413
|
+
elif args.cmd == "notify":
|
|
414
|
+
do_notify()
|
|
415
|
+
elif args.cmd == "predict":
|
|
416
|
+
do_predict()
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
if __name__ == "__main__":
|
|
420
|
+
main()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xfc-client
|
|
3
|
+
Version: 1.1.2
|
|
4
|
+
Summary: Client library and command line tool for the Transfer Cache (XFC).
|
|
5
|
+
Home-page: http://www.ceda.ac.uk/
|
|
6
|
+
Author: Neil Massey
|
|
7
|
+
Author-email: Neil Massey <neil.massey@stfc.ac.uk>
|
|
8
|
+
License-Expression: BSD-2-Clause
|
|
9
|
+
Project-URL: Homepage, https://github.com/cedadev/xfc_client
|
|
10
|
+
Project-URL: Issues, https://github.com/cedadev/xfc_client/issues
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Requires-Python: >=3.14
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: certifi>=2026.10.19
|
|
17
|
+
Requires-Dist: charset-normalizer>=3.4.2
|
|
18
|
+
Requires-Dist: idna>=3.15
|
|
19
|
+
Requires-Dist: python-dateutil>=2.9.0.post0
|
|
20
|
+
Requires-Dist: pip>=26.2.2
|
|
21
|
+
Requires-Dist: requests>=2.33.0
|
|
22
|
+
Requires-Dist: six>=1.17.0
|
|
23
|
+
Requires-Dist: urllib3>=2.8.0
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: home-page
|
|
26
|
+
Dynamic: license-file
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
xfc_client/__init__.py,sha256=tr2dMUFshB0V7YlDRbNgwbPa_pUX5HAgJ388cVKIj1Y,18
|
|
2
|
+
xfc_client/xfc.py,sha256=X2JxH68zTJBLHMqlMz2FThhS_sGcZLrEwQXmrql-qvE,12882
|
|
3
|
+
xfc_client-1.1.2.dist-info/licenses/LICENSE,sha256=4FSft38MOrMdPfR69VpEfNo1_8Fyn44ZaaHnXRbhS4w,1517
|
|
4
|
+
xfc_client-1.1.2.dist-info/METADATA,sha256=s4WlJGU2LkPYHn3oALnaD1DX4DFSPILsBJq6WbYIEMo,895
|
|
5
|
+
xfc_client-1.1.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
6
|
+
xfc_client-1.1.2.dist-info/entry_points.txt,sha256=ibbhGRdMPn93JYRHJsDNoYoqfXjny6Vsnn9Ze3KAqiU,44
|
|
7
|
+
xfc_client-1.1.2.dist-info/top_level.txt,sha256=w8KYywnkriWMgvcE2loin6VNngsXIUt0qu0gDXZ0q4E,11
|
|
8
|
+
xfc_client-1.1.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Copyright 2018 United Kingdom Research and Innovation
|
|
2
|
+
|
|
3
|
+
Authors: Neil Massey
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
8
|
+
list of conditions and the following disclaimer.
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
13
|
+
may be used to endorse or promote products derived from this software
|
|
14
|
+
without specific prior written permission.
|
|
15
|
+
|
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
17
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
18
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
19
|
+
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
20
|
+
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
21
|
+
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
22
|
+
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
23
|
+
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
24
|
+
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
25
|
+
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
26
|
+
POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xfc_client
|