bbarchivist.scriptutilstcl   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 451
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 144
dl 0
loc 451
rs 8.8798
c 0
b 0
f 0
ccs 143
cts 143
cp 1
wmc 44

20 Functions

Rating   Name   Duplication   Size   Complexity  
A tcl_findprd_safehandle() 0 14 1
A tcl_findprd_prepd_end() 0 12 3
A tcl_findprd_prepd_start() 0 11 2
A tcl_prep_otaver() 0 14 2
A tcl_findprd_prepdict() 0 11 1
A tcl_prd_print() 0 31 3
A tcl_findprd_safescan() 0 30 3
A tcl_mainscan_preamble() 0 10 2
A tcl_findprd_prepcuref() 0 22 3
A tcl_findprd_checkfilter() 0 17 3
A tcl_delta_filename() 0 23 2
A tclloader_filename() 0 17 2
A tcl_prd_scan() 0 39 4
A tcl_delta_remote() 0 13 2
A tclloader_prep() 0 13 2
A tcl_download() 0 32 3
A tcl_mainscan_printer() 0 17 2
A tcl_findprd() 0 25 2
A tcl_findprd_prepd_middle() 0 10 1
A tcl_findprd_centerscan() 0 32 1

How to fix   Complexity   

Complexity

Complex classes like bbarchivist.scriptutilstcl often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python3
2 5
"""This module contains various utilities for TCL tools."""
3
4 5
import collections  # defaultdict
5 5
import os  # path work
6
7 5
import requests  # session
8 5
from bbarchivist import argutils  # arguments
9 5
from bbarchivist import hashutils  # file hashes
10 5
from bbarchivist import networkutils  # network tools
11 5
from bbarchivist import networkutilstcl  # tcl network tools
12 5
from bbarchivist import utilities  # little things
13 5
from bbarchivist import xmlutilstcl  # xml handling
14
15 5
__author__ = "Thurask"
16 5
__license__ = "WTFPL v2"
17 5
__copyright__ = "2018-2019 Thurask"
18
19
20 5
def tclloader_prep(loaderfile, directory=False):
21
    """
22
    Prepare directory name and OS version.
23
24
    :param loaderfile: Path to input file/folder.
25
    :type loaderfile: str
26
27
    :param directory: If the input file is a folder. Default is False.
28
    :type directory: bool
29
    """
30 5
    loaderdir = loaderfile if directory else loaderfile.replace(".zip", "")
31 5
    osver = loaderdir.split("-")[-1]
32 5
    return loaderdir, osver
33
34
35 5
def tclloader_filename(loaderdir, osver, loadername=None):
36
    """
37
    Prepare platform and filename.
38
39
    :param loaderdir: Path to input folder.
40
    :type loaderdir: str
41
42
    :param osver: OS version.
43
    :type osver: str
44
45
    :param loadername: Name of final autoloader. Default is auto-generated.
46
    :type loadername: str
47
    """
48 5
    platform = os.listdir(os.path.join(loaderdir, "target", "product"))[0]
49 5
    if loadername is None:
50 5
        loadername = "{0}_autoloader_user-all-{1}".format(platform, osver)
51 5
    return loadername, platform
52
53
54 5
def tcl_download(downloadurl, filename, filesize, filehash, verify=True):
55
    """
56
    Download autoloader file, rename, and verify.
57
58
    :param downloadurl: Download URL.
59
    :type downloadurl: str
60
61
    :param filename: Name of autoloader file.
62
    :type filename: str
63
64
    :param filesize: Size of autoloader file.
65
    :type filesize: str
66
67
    :param filehash: SHA-1 hash of autoloader file.
68
    :type filehash: str
69
70
    :param verify: Whether to verify the file after downloading. Default is True.
71
    :type verify: bool
72
    """
73 5
    print("FILENAME: {0}".format(filename))
74 5
    print("LENGTH: {0}".format(utilities.fsizer(filesize)))
75 5
    networkutils.download(downloadurl)
76 5
    print("DOWNLOAD COMPLETE")
77 5
    os.rename(downloadurl.split("/")[-1], filename)
78 5
    if verify:
79 5
        method = hashutils.get_engine("sha1")
80 5
        shahash = hashutils.hashlib_hash(filename, method)
81 5
        if shahash == filehash:
82 5
            print("HASH CHECK OK")
83
        else:
84 5
            print(shahash)
85 5
            print("HASH FAILED!")
86
87
88 5
def tcl_prd_scan(curef, download=False, mode=4, fvver="AAA000", original=True, export=False, verify=True):
89
    """
90
    Scan one PRD and produce download URL and filename.
91
92
    :param curef: PRD of the phone variant to check.
93
    :type curef: str
94
95
    :param download: If we'll download the file that this returns. Default is False.
96
    :type download: bool
97
98
    :param mode: 4 if downloading autoloaders, 2 if downloading OTA deltas.
99
    :type mode: int
100
101
    :param fvver: Initial software version, must be specific if downloading OTA deltas.
102
    :type fvver: str
103
104
    :param original: If we'll download the file with its original filename. Default is True.
105
    :type original: bool
106
107
    :param export: Whether to export XML response to file. Default is False.
108
    :type export: bool
109
110
    :param verify: Whether to verify the file after downloading. Default is True.
111
    :type verify: bool
112
    """
113 5
    sess = requests.Session()
114 5
    ctext = networkutilstcl.tcl_check(curef, sess, mode, fvver, export)
115 5
    if ctext is None:
116 5
        raise SystemExit
117 5
    tvver, firmwareid, filename, filesize, filehash = xmlutilstcl.parse_tcl_check(ctext)
118 5
    salt = networkutilstcl.tcl_salt()
119 5
    vkhsh = networkutilstcl.vkhash(curef, tvver, firmwareid, salt, mode, fvver)
120 5
    updatetext = networkutilstcl.tcl_download_request(curef, tvver, firmwareid, salt, vkhsh, sess, mode, fvver, export)
121 5
    downloadurl, encslave = xmlutilstcl.parse_tcl_download_request(updatetext)
122 5
    statcode = networkutils.getcode(downloadurl, sess)
123 5
    filename = tcl_delta_filename(curef, fvver, tvver, filename, original)
124 5
    tcl_prd_print(tvver, downloadurl, filename, statcode, encslave, sess)
125 5
    if statcode == 200 and download:
126 5
        tcl_download(downloadurl, filename, filesize, filehash, verify)
127
128
129 5
def tcl_delta_filename(curef, fvver, tvver, filename, original=True):
130
    """
131
    Generate compatible filenames for deltas, if needed.
132
133
    :param curef: PRD of the phone variant to check.
134
    :type curef: str
135
136
    :param fvver: Initial software version.
137
    :type fvver: str
138
139
    :param tvver: Target software version.
140
    :type tvver: str
141
142
    :param filename: File name from download URL, passed through if not changing filename.
143
    :type filename: str
144
145
    :param original: If we'll download the file with its original filename. Default is True.
146
    :type original: bool
147
    """
148 5
    if not original:
149 5
        prdver = curef.split("-")[1]
150 5
        filename = "JSU_PRD-{0}-{1}to{2}.zip".format(prdver, fvver, tvver)
151 5
    return filename
152
153
154 5
def tcl_prd_print(tvver, downloadurl, filename, statcode, encslave, session):
155
    """
156
    Print output from PRD scanning.
157
158
    :param tvver: Target software version.
159
    :type tvver: str
160
161
    :param downloadurl: File to download.
162
    :type downloadurl: str
163
164
    :param filename: File name from download URL.
165
    :type filename: str
166
167
    :param statcode: Status code of download URL.
168
    :type statcode: int
169
170
    :param encslave: Server hosting header script.
171
    :type encslave: str
172
173
    :param session: Session object.
174
    :type session: requests.Session
175
    """
176 5
    print("OS: {0}".format(tvver))
177 5
    print("{0}: HTTP {1}".format(filename, statcode))
178 5
    print(downloadurl)
179 5
    if encslave is not None:
180 5
        address = "/{0}".format(downloadurl.split("/", 3)[3:][0])
181 5
        print("CHECKING HEADER...")
182 5
        sentinel = networkutilstcl.encrypt_header(address, encslave, session)
183 5
        if sentinel is not None:
184 5
            print(sentinel)
185
186
187 5
def tcl_prep_otaver(ota=None):
188
    """
189
    Prepare variables for OTA versus full check.
190
191
    :param ota: The starting version if OTA, None if not. Default is None.
192
    :type ota: str
193
    """
194 5
    if ota is not None:
195 5
        mode = 2
196 5
        fvver = ota
197
    else:
198 5
        mode = 4
199 5
        fvver = "AAA000"
200 5
    return mode, fvver
201
202
203 5
def tcl_delta_remote(curef):
204
    """
205
    Prepare remote version for delta scanning.
206
207
    :param curef: PRD of the phone variant to check.
208
    :type curef: str
209
    """
210 5
    remotedict = networkutilstcl.remote_prd_info()
211 5
    fvver = remotedict.get(curef, "AAA000")
212 5
    if fvver == "AAA000":
213 5
        print("NO REMOTE VERSION FOUND!")
214 5
        raise SystemExit
215 5
    return fvver
216
217
218 5
def tcl_mainscan_preamble(ota=None):
219
    """
220
    Prepare preamble for TCL scanning.
221
222
    :param ota: The starting version if OTA, None if not. Default is None.
223
    :type ota: str
224
    """
225 5
    argutils.slim_preamble("TCLSCAN")
226 5
    if ota is not None:
227 5
        print("PRDs with OTA from OS {0}".format(ota.upper()))
228
229
230 5
def tcl_mainscan_printer(curef, tvver, ota=None):
231
    """
232
    Print output of TCL scanning.
233
234
    :param curef: PRD of the phone variant to check.
235
    :type curef: str
236
237
    :param tvver: Target software version.
238
    :type tvver: str
239
240
    :param ota: The starting version if OTA, None if not. Default is None.
241
    :type ota: str
242
    """
243 5
    if ota is not None:
244 5
        print("{0}: {2} to {1}".format(curef, tvver, ota.upper()))
245
    else:
246 5
        print("{0}: {1}".format(curef, tvver))
247
248
249 5
def tcl_findprd_prepd_start(prddict):
250
    """
251
    Collect list of PRD entries.
252
253
    :param prddict: Device:PRD dictionary.
254
    :type prddict: dict(str: list)
255
    """
256 5
    prda = []
257 5
    for item in prddict.values():
258 5
        prda.extend(item)
259 5
    return prda
260
261
262 5
def tcl_findprd_prepd_middle(prda):
263
    """
264
    Convert PRD entries to list of center:end entries.
265
266
    :param prda: List of PRD-xxxxx-yyy entries.
267
    :type prda: list(str)
268
    """
269 5
    prds = [x.split(" ")[0].replace("PRD-", "").replace("APBI-PRD", "").replace("-", "") for x in prda]
270 5
    prdx = list({x[0:5]: x[5:]} for x in prds)
271 5
    return prdx
272
273
274 5
def tcl_findprd_prepd_end(prdx):
275
    """
276
    Convert list of center:end entries to final center:[ends] dict.
277
278
    :param prdx: List of center:end dict entries.
279
    :type prdx: list(dict(str: str))
280
    """
281 5
    prdf = collections.defaultdict(list)
282 5
    for prdc in prdx:
283 5
        for key, value in prdc.items():
284 5
            prdf[key].append(value)
285 5
    return prdf
286
287
288 5
def tcl_findprd_prepdict(prddict):
289
    """
290
    Prepare dict of center:[ends] entries.
291
292
    :param prddict: Device:PRD dictionary.
293
    :type prddict: dict(str: list)
294
    """
295 5
    prda = tcl_findprd_prepd_start(prddict)
296 5
    prdx = tcl_findprd_prepd_middle(prda)
297 5
    prdfinal = tcl_findprd_prepd_end(prdx)
298 5
    return prdfinal
299
300
301 5
def tcl_findprd_checkfilter(prddict, tocheck=None):
302
    """
303
    Filter PRD dict if needed.
304
305
    :param prddict: PRD center:[ends] dictionary.
306
    :type prddict: collections.defaultdict(str: list)
307
308
    :param tocheck: Specific PRD(s) to check, None if all will be checked. Default is None.
309
    :type tocheck: list(str)
310
    """
311 5
    prddict2 = prddict
312 5
    if tocheck is not None:
313 5
        prddict2 = collections.defaultdict(list)
314 5
        for toch in tocheck:
315 5
            toch = toch.replace("PRD-", "").replace("APBI-PRD", "")
316 5
            prddict2[toch] = prddict[toch]
317 5
    return prddict2
318
319
320 5
def tcl_findprd_centerscan(center, prddict, session, floor=0, ceiling=999, export=False, noprefix=False, key2mode=False):
321
    """
322
    Individual scanning for the center of a PRD.
323
324
    :param center: PRD-center-end.
325
    :type center: str
326
327
    :param prddict: PRD center:[ends] dictionary.
328
    :type prddict: collections.defaultdict(str: list)
329
330
    :param session: Session object.
331
    :type session: requests.Session
332
333
    :param floor: When to start. Default is 0.
334
    :type floor: int
335
336
    :param ceiling: When to stop. Default is 999.
337
    :type ceiling: int
338
339
    :param export: Whether to export XML response to file. Default is False.
340
    :type export: bool
341
342
    :param noprefix: Whether to skip adding "PRD-" prefix. Default is False.
343
    :type noprefix: bool
344
345
    :param key2mode: Whether to use new-style prefix. Default is False.
346
    :type key2mode: bool
347
    """
348 5
    tails = [int(i) for i in prddict[center]]
349 5
    safes = [g for g in range(floor, ceiling) if g not in tails]
350 5
    print("SCANNING ROOT: {0}{1}".format(center, " "*12))
351 5
    tcl_findprd_safescan(safes, center, session, export, noprefix, key2mode)
352
353
354 5
def tcl_findprd_prepcuref(center, tail, noprefix=False, key2mode=False):
355
    """
356
    Prepare candidate PRD.
357
358
    :param center: PRD-center-tail.
359
    :type center: str
360
361
    :param tail: PRD-center-tail.
362
    :type tail: int
363
364
    :param noprefix: Whether to skip adding "PRD-" prefix. Default is False.
365
    :type noprefix: bool
366
367
    :param key2mode: Whether to use new-style prefix. Default is False.
368
    :type key2mode: bool
369
    """
370 5
    if key2mode:
371 5
        curef = "APBI-PRD{0}{1:03}".format(center, tail)
372
    else:
373 5
        prefix = "" if noprefix else "PRD-"
374 5
        curef = "{2}{0}-{1:03}".format(center, tail, prefix)
375 5
    return curef
376
377
378 5
def tcl_findprd_safescan(safes, center, session, export=False, noprefix=False, key2mode=False):
379
    """
380
    Scan for PRDs known not to be in database.
381
382
    :param safes: List of ends within given range that aren't in database.
383
    :type safes: list(int)
384
385
    :param center: PRD-center-end.
386
    :type center: str
387
388
    :param session: Session object.
389
    :type session: requests.Session
390
391
    :param export: Whether to export XML response to file. Default is False.
392
    :type export: bool
393
394
    :param noprefix: Whether to skip adding "PRD-" prefix. Default is False.
395
    :type noprefix: bool
396
397
    :param key2mode: Whether to use new-style prefix. Default is False.
398
    :type key2mode: bool
399
    """
400 5
    for j in safes:
401 5
        curef = tcl_findprd_prepcuref(center, j, noprefix, key2mode)
402 5
        print("NOW SCANNING: {0}".format(curef), end="\r")
403 5
        checktext = networkutilstcl.tcl_check(curef, session, export)
404 5
        if checktext is None:
405 5
            continue
406
        else:
407 5
            tcl_findprd_safehandle(curef, checktext)
408
409
410 5
def tcl_findprd_safehandle(curef, checktext):
411
    """
412
    Parse API output and print the relevant bits.
413
414
    :param curef: PRD of the phone variant to check.
415
    :type curef: str
416
417
    :param checktext: The XML formatted data returned from the first stage API check.
418
    :type checktext: str
419
    """
420 5
    tvver, firmwareid, filename, fsize, fhash = xmlutilstcl.parse_tcl_check(checktext)
421 5
    del firmwareid, filename, fsize, fhash
422 5
    tvver2 = "{0}{1}".format(tvver, " "*12)
423 5
    tcl_mainscan_printer(curef, tvver2)
424
425
426 5
def tcl_findprd(prddict, floor=0, ceiling=999, export=False, noprefix=False, key2mode=False):
427
    """
428
    Check for new PRDs based on PRD database.
429
430
    :param prddict: PRD center:[ends] dictionary.
431
    :type prddict: collections.defaultdict(str: list)
432
433
    :param floor: When to start. Default is 0.
434
    :type floor: int
435
436
    :param ceiling: When to stop. Default is 999.
437
    :type ceiling: int
438
439
    :param export: Whether to export XML response to file. Default is False.
440
    :type export: bool
441
442
    :param noprefix: Whether to skip adding "PRD-" prefix. Default is False.
443
    :type noprefix: bool
444
445
    :param key2mode: Whether to use new-style prefix. Default is False.
446
    :type key2mode: bool
447
    """
448 5
    sess = requests.Session()
449 5
    for center in sorted(prddict.keys()):
450
        tcl_findprd_centerscan(center, prddict, sess, floor, ceiling, export, noprefix, key2mode)
451