Passed
Push — master ( a829b5...274f62 )
by John
02:16
created

bbarchivist.scriptutils.kernchecker_dict()   A

Complexity

Conditions 3

Size

Total Lines 14
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 5
nop 2
dl 0
loc 14
ccs 5
cts 5
cp 1
crap 3
rs 9.4285
c 0
b 0
f 0
1
#!/usr/bin/env python3
0 ignored issues
show
coding-style introduced by
Too many lines in module (1641/1000)
Loading history...
2 5
"""This module contains various utilities for the scripts folder."""
3
4 5
import collections  # defaultdict
5 5
import getpass  # invisible password
6 5
import hashlib  # hashes
7 5
import os  # path work
8 5
import shutil  # folder removal
9 5
import sys  # getattr
10 5
import threading  # run stuff in background
11
12 5
import requests  # session
13 5
from bbarchivist import argutils  # arguments
14 5
from bbarchivist import archiveutils  # archive support
15 5
from bbarchivist import barutils  # file system work
16 5
from bbarchivist import bbconstants  # constants
17 5
from bbarchivist import decorators  # decorating functions
18 5
from bbarchivist import gpgutils  # gpg
19 5
from bbarchivist import hashutils  # file hashes
20 5
from bbarchivist import networkutils  # network tools
21 5
from bbarchivist import smtputils  # email
22 5
from bbarchivist import sqlutils  # sql
23 5
from bbarchivist import textgenerator  # writing text to file
24 5
from bbarchivist import utilities  # little things
25 5
from bbarchivist import xmlutils  # xml handling
26
27 5
__author__ = "Thurask"
28 5
__license__ = "WTFPL v2"
29 5
__copyright__ = "2015-2018 Thurask"
30
31
32 5
def return_radio_version(osversion, radioversion=None):
33
    """
34
    Increment radio version, if need be.
35
36
    :param osversion: OS version.
37
    :type osversion: str
38
39
    :param radioversion: Radio version, None if incremented.
40
    :type radioversion: str
41
    """
42 5
    if radioversion is None:
43 5
        radioversion = utilities.increment(osversion, 1)
44 5
    return radioversion
45
46
47 5
def sw_check_contingency(softwareversion):
48
    """
49
    Ask in the event software release isn't found.
50
51
    :param softwareversion: Software release version.
52
    :type softwareversion: str
53
    """
54 5
    if softwareversion == "SR not in system":
55 5
        print("SOFTWARE RELEASE NOT FOUND")
56 5
        cont = utilities.i2b("INPUT MANUALLY? Y/N: ")
57 5
        if cont:
58 5
            softwareversion = input("SOFTWARE RELEASE: ")
59 5
            swchecked = False
60
        else:
61 5
            print("\nEXITING...")
62 5
            raise SystemExit  # bye bye
63
    else:
64 5
        swchecked = True
65 5
    return softwareversion, swchecked
66
67
68 5
def return_sw_checked(softwareversion, osversion):
69
    """
70
    Check software existence, return boolean.
71
72
    :param softwareversion: Software release version.
73
    :type softwareversion: str
74
75
    :param osversion: OS version.
76
    :type osversion: str
77
    """
78 5
    if softwareversion is None:
79 5
        serv = bbconstants.SERVERS["p"]
80 5
        softwareversion = networkutils.sr_lookup(osversion, serv)
81 5
        softwareversion, swchecked = sw_check_contingency(softwareversion)
82
    else:
83 5
        swchecked = True
84 5
    return softwareversion, swchecked
85
86
87 5
def return_radio_sw_checked(altsw, radioversion):
88
    """
89
    Check radio software existence, return boolean.
90
91
    :param altsw: Software release version.
92
    :type altsw: str
93
94
    :param radioversion: Radio version.
95
    :type radioversion: str
96
    """
97 5
    if altsw == "checkme":
98 5
        serv = bbconstants.SERVERS["p"]
99 5
        testos = utilities.increment(radioversion, -1)
100 5
        altsw = networkutils.sr_lookup(testos, serv)
101 5
        altsw, altchecked = sw_check_contingency(altsw)
102
    else:
103 5
        altchecked = True
104 5
    return altsw, altchecked
105
106
107 5
def check_sw(baseurl, softwareversion, swchecked, altsw=False):
108
    """
109
    Check existence of software release.
110
111
    :param baseurl: Base URL (from http to hashed SW release).
112
    :type baseurl: str
113
114
    :param softwareversion: Software release.
115
    :type softwareversion: str
116
117
    :param swchecked: If we checked the sw release already.
118
    :type swchecked: bool
119
120
    :param altsw: If this is the radio-only release. Default is false.
121
    :type altsw: bool
122
    """
123 5
    message = "CHECKING RADIO SOFTWARE RELEASE..." if altsw else "CHECKING SOFTWARE RELEASE..."
124 5
    print(message)
125 5
    if not swchecked:
126 5
        check_sw_actual(baseurl, softwareversion)
127
    else:
128 5
        print("SOFTWARE RELEASE {0} EXISTS".format(softwareversion))
129
130
131 5
def check_sw_actual(baseurl, softwareversion):
132
    """
133
    Get the status of a software release.
134
135
    :param baseurl: Base URL (from http to hashed SW release).
136
    :type baseurl: str
137
138
    :param softwareversion: Software release.
139
    :type softwareversion: str
140
    """
141 5
    avlty = networkutils.availability(baseurl)
142 5
    if avlty:
143 5
        print("SOFTWARE RELEASE {0} EXISTS".format(softwareversion))
144
    else:
145 5
        check_sw_handle(softwareversion)
146
147
148 5
def check_sw_handle(softwareversion):
149
    """
150
    Handle non-existent software release.
151
152
    :param softwareversion: Software release.
153
    :type softwareversion: str
154
    """
155 5
    print("SOFTWARE RELEASE {0} NOT FOUND".format(softwareversion))
156 5
    cont = utilities.i2b("CONTINUE? Y/N: ")
157 5
    if not cont:
158 5
        print("\nEXITING...")
159 5
        raise SystemExit
160
161
162 5
def check_radio_sw(alturl, altsw, altchecked):
163
    """
164
    Check existence of radio software release.
165
166
    :param alturl: Radio base URL (from http to hashed SW release).
167
    :type alturl: str
168
169
    :param altsw: Radio software release.
170
    :type altsw: str
171
172
    :param altchecked: If we checked the sw release already.
173
    :type altchecked: bool
174
    """
175 5
    return check_sw(alturl, altsw, altchecked, True)
176
177
178 5
def check_altsw(altcheck=False):
179
    """
180
    Ask for and return alternate software release, if needed.
181
182
    :param altcheck: If we're using an alternate software release.
183
    :type altcheck: bool
184
    """
185 5
    if altcheck:
186 5
        altsw = input("RADIO SOFTWARE RELEASE (PRESS ENTER TO GUESS): ")
187 5
        if not altsw:
188 5
            altsw = "checkme"
189
    else:
190 5
        altsw = None
191 5
    return altsw
192
193
194 5
def check_os_single(osurl, osversion, device):
195
    """
196
    Check existence of single OS link.
197
198
    :param radiourl: Radio URL to check.
199
    :type radiourl: str
200
201
    :param radioversion: Radio version.
202
    :type radioversion: str
203
204
    :param device: Device family.
205
    :type device: int
206
    """
207 5
    osav = networkutils.availability(osurl)
208 5
    if not osav:
209 5
        print("{0} NOT AVAILABLE FOR {1}".format(osversion, bbconstants.DEVICES[device]))
210 5
        cont = utilities.i2b("CONTINUE? Y/N: ")
211 5
        if not cont:
212 5
            print("\nEXITING...")
213 5
            raise SystemExit
214
215
216 5
def check_os_bulk(osurls):
217
    """
218
    Check existence of list of OS links.
219
220
    :param osurls: OS URLs to check.
221
    :type osurls: list(str)
222
    """
223 5
    sess = requests.Session()
224 5
    for url in osurls:
225 5
        osav = networkutils.availability(url, sess)
226 5
        if osav:
227 5
            break
228
    else:
229 5
        check_os_bulk_handle()
230
231
232 5
def check_os_bulk_handle():
233
    """
234
    Handle no existing OS links.
235
    """
236 5
    print("OS VERSION NOT FOUND")
237 5
    cont = utilities.i2b("CONTINUE? Y/N: ")
238 5
    if not cont:
239 5
        print("\nEXITING...")
240 5
        raise SystemExit
241
242
243 5
def check_radio_single(radiourl, radioversion):
244
    """
245
    Check existence of single radio link.
246
247
    :param radiourl: Radio URL to check.
248
    :type radiourl: str
249
250
    :param radioversion: Radio version.
251
    :type radioversion: str
252
    """
253 5
    radav = networkutils.availability(radiourl)
254 5
    if not radav:
255 5
        print("RADIO VERSION NOT FOUND")
256 5
        cont = utilities.i2b("INPUT MANUALLY? Y/N: ")
257 5
        if cont:
258 5
            rad2 = input("RADIO VERSION: ")
259 5
            radiourl = radiourl.replace(radioversion, rad2)
260 5
            radioversion = rad2
261
        else:
262 5
            going = utilities.i2b("KEEP GOING? Y/N: ")
263 5
            if not going:
264 5
                print("\nEXITING...")
265 5
                raise SystemExit
266 5
    return radiourl, radioversion
267
268
269 5
def check_radio_bulk(radiourls, radioversion):
270
    """
271
    Check existence of list of radio links.
272
273
    :param radiourls: Radio URLs to check.
274
    :type radiourls: list(str)
275
276
    :param radioversion: Radio version.
277
    :type radioversion: str
278
    """
279 5
    sess = requests.Session()
280 5
    for url in radiourls:
281 5
        radav = networkutils.availability(url, sess)
282 5
        if radav:
283 5
            break
284
    else:
285 5
        radiourls, radioversion = check_radio_bulk_notfound(radiourls, radioversion)
286 5
    return radiourls, radioversion
287
288
289 5
def check_radio_bulk_notfound(radiourls, radioversion):
290
    """
291
    What to do if radio links aren't found.
292
293
    :param radiourls: Radio URLs to check.
294
    :type radiourls: list(str)
295
296
    :param radioversion: Radio version.
297
    :type radioversion: str
298
    """
299 5
    print("RADIO VERSION NOT FOUND")
300 5
    cont = utilities.i2b("INPUT MANUALLY? Y/N: ")
301 5
    if cont:
302 5
        radiourls, radioversion = check_radio_bulk_go(radiourls, radioversion)
303
    else:
304 5
        check_radio_bulk_stop()
305 5
    return radiourls, radioversion
306
307
308 5
def check_radio_bulk_go(radiourls, radioversion):
309
    """
310
    Replace radio version and URLs, and keep going.
311
312
    :param radiourls: Radio URLs to check.
313
    :type radiourls: list(str)
314
315
    :param radioversion: Radio version.
316
    :type radioversion: str
317
    """
318 5
    rad2 = input("RADIO VERSION: ")
319 5
    radiourls = [url.replace(radioversion, rad2) for url in radiourls]
320 5
    radioversion = rad2
321 5
    return radiourls, radioversion
322
323
324 5
def check_radio_bulk_stop():
325
    """
326
    Ask if we should keep going once no radio has been found.
327
    """
328 5
    going = utilities.i2b("KEEP GOING? Y/N: ")
329 5
    if not going:
330 5
        print("\nEXITING...")
331 5
        raise SystemExit
332
333
334 5
def bulk_avail(urllist):
335
    """
336
    Filter 404 links out of URL list.
337
338
    :param urllist: URLs to check.
339
    :type urllist: list(str)
340
    """
341 5
    sess = requests.Session()
342 5
    url2 = [x for x in urllist if networkutils.availability(x, sess)]
343 5
    return url2
344
345
346 5
def get_baseurls(softwareversion, altsw=None):
347
    """
348
    Generate base URLs for bar links.
349
350
    :param softwareversion: Software version.
351
    :type softwareversion: str
352
353
    :param altsw: Radio software version, if necessary.
354
    :type altsw: str
355
    """
356 5
    baseurl = utilities.create_base_url(softwareversion)
357 5
    alturl = utilities.create_base_url(altsw) if altsw else None
358 5
    return baseurl, alturl
359
360
361 5
def get_sz_executable(compmethod):
362
    """
363
    Get 7z executable.
364
365
    :param compmethod: Compression method.
366
    :type compmethod: str
367
    """
368 5
    if compmethod != "7z":
369 5
        szexe = ""
370
    else:
371 5
        print("CHECKING PRESENCE OF 7ZIP...")
372 5
        psz = utilities.prep_seven_zip(True)
373 5
        if psz:
374 5
            print("7ZIP OK")
375 5
            szexe = utilities.get_seven_zip(False)
376
        else:
377 5
            szexe = ""
378 5
            print("7ZIP NOT FOUND")
379 5
            cont = utilities.i2b("CONTINUE? Y/N ")
380 5
            if cont:
381 5
                print("FALLING BACK TO ZIP...")
382 5
                compmethod = "zip"
383
            else:
384 5
                print("\nEXITING...")
385 5
                raise SystemExit  # bye bye
386 5
    return compmethod, szexe
387
388
389 5
def test_bar_files(localdir, urllist):
390
    """
391
    Test bar files after download.
392
393
    :param localdir: Directory.
394
    :type localdir: str
395
396
    :param urllist: List of URLs to check.
397
    :type urllist: list(str)
398
    """
399 5
    print("TESTING BAR FILES...")
400 5
    brokenlist = []
401 5
    for file in os.listdir(localdir):
402 5
        brokenlist = test_bar_files_individual(file, localdir, urllist, brokenlist)
403 5
    if brokenlist:
404 5
        print("SOME FILES ARE BROKEN!")
405 5
        utilities.lprint(brokenlist)
406 5
        raise SystemExit
407
    else:
408 5
        print("BAR FILES DOWNLOADED OK")
409
410
411 5
def test_bar_files_individual(file, localdir, urllist, brokenlist):
412
    """
413
    Test bar file after download.
414
415
    :param file: Bar file to check.
416
    :type file: str
417
418
    :param localdir: Directory.
419
    :type localdir: str
420
421
    :param urllist: List of URLs to check.
422
    :type urllist: list(str)
423
424
    :param brokenlist: List of URLs to download later.
425
    :type brokenlist: list(str)
426
    """
427 5
    if file.endswith(".bar"):
428 5
        print("TESTING: {0}".format(file))
429 5
        thepath = os.path.abspath(os.path.join(localdir, file))
430 5
        brokens = barutils.bar_tester(thepath)
431 5
        brokenlist = bar_broken_individual(brokens, urllist, brokenlist)
432 5
    return brokenlist
433
434
435 5
def bar_broken_individual(brokens, urllist, brokenlist):
436
    """
437
    What to do if a downloaded bar file is broken.
438
439
    :param brokens: None if bar is OK, filename if it is not.
440
    :type brokens: str
441
442
    :param urllist: List of URLs to check.
443
    :type urllist: list(str)
444
445
    :param brokenlist: List of URLs to download later.
446
    :type brokenlist: list(str)
447
    """
448 5
    if brokens is not None:
449 5
        os.remove(brokens)
450 5
        for url in urllist:
451 5
            if brokens in url:
452 5
                brokenlist.append(url)
453 5
    return brokenlist
454
455
456 5
def test_signed_files(localdir):
457
    """
458
    Test signed files after extract.
459
460
    :param localdir: Directory.
461
    :type localdir: str
462
    """
463 5
    print("TESTING SIGNED FILES...")
464 5
    for file in os.listdir(localdir):
465 5
        if file.endswith(".bar"):
466 5
            print("TESTING: {0}".format(file))
467 5
            signname, signhash = barutils.retrieve_sha512(os.path.join(localdir, file))
468 5
            sha512ver = barutils.verify_sha512(os.path.join(localdir, signname.decode("utf-8")), signhash)
469 5
            if not sha512ver:
470 5
                print("{0} IS BROKEN".format((file)))
471 5
                break
472
    else:
473 5
        print("ALL FILES EXTRACTED OK")
474
475
476 5
def test_loader_files(localdir):
477
    """
478
    Test loader files after creation.
479
480
    :param localdir: Directory.
481
    :type localdir: str
482
    """
483 5
    if not utilities.is_windows():
484 5
        pass
485
    else:
486 5
        print("TESTING LOADER FILES...")
487 5
        brokens = utilities.verify_bulk_loaders(localdir)
488 5
        if brokens:
489 5
            print("BROKEN FILES:")
490 5
            utilities.lprint(brokens)
491 5
            raise SystemExit
492
        else:
493 5
            print("ALL FILES CREATED OK")
494
495
496 5
def test_single_loader(loaderfile):
497
    """
498
    Test single loader file after creation.
499
500
    :param loaderfile: File to check.
501
    :type loaderfile: str
502
    """
503 5
    if not utilities.is_windows():
504 5
        pass
505
    else:
506 5
        print("TESTING LOADER...")
507 5
        if not utilities.verify_loader_integrity(loaderfile):
508 5
            print("{0} IS BROKEN!".format(os.path.basename(loaderfile)))
509 5
            raise SystemExit
510
        else:
511 5
            print("LOADER CREATED OK")
512
513
514 5
def prod_avail(results, mailer=False, osversion=None, password=None):
515
    """
516
    Clean availability for production lookups for autolookup script.
517
518
    :param results: Result dict.
519
    :type results: dict(str: str)
520
521
    :param mailer: If we're mailing links. Default is false.
522
    :type mailer: bool
523
524
    :param osversion: OS version.
525
    :type osversion: str
526
527
    :param password: Email password.
528
    :type password: str
529
    """
530 5
    prel = results['p']
531 5
    if prel != "SR not in system" and prel is not None:
532 5
        pav = "PD"
533 5
        baseurl = utilities.create_base_url(prel)
534 5
        avail = networkutils.availability(baseurl)
535 5
        is_avail = "Available" if avail else "Unavailable"
536 5
        prod_avail_mailprep(prel, avail, osversion, mailer, password)
537
    else:
538 5
        pav = "  "
539 5
        is_avail = "Unavailable"
540 5
    return prel, pav, is_avail
541
542
543 5
def prod_avail_mailprep(prel, avail, osversion=None, mailer=False, password=None):
544
    """
545
    Do SQL/SMTP prep work after a good production lookup hit.
546
547
    :param prel: Software lookup result.
548
    :type prel: str
549
550
    :param avail: If software lookup result is available for download.
551
    :type avail: bool
552
553
    :param osversion: OS version.
554
    :type osversion: str
555
556
    :param mailer: If we're mailing links. Default is false.
557
    :type mailer: bool
558
559
    :param password: Email password.
560
    :type password: str
561
    """
562 5
    if avail and mailer:
563 5
        sqlutils.prepare_sw_db()
564 5
        if not sqlutils.check_exists(osversion, prel):
565 5
            rad = utilities.increment(osversion, 1)
566 5
            linkgen(osversion, rad, prel, temp=True)
567 5
            smtputils.prep_email(osversion, prel, password)
568
569
570 5
def comp_joiner(rootdir, localdir, filelist):
571
    """
572
    Join rootdir, localdir to every file in filelist.
573
574
    :param rootdir: Root directory.
575
    :type rootdir: str
576
577
    :param localdir: Subfolder inside rootdir.
578
    :type localdir: str
579
580
    :param filelist: List of files to return this path for.
581
    :type filelist: list(str)
582
    """
583 5
    joinedfiles = [os.path.join(rootdir, localdir, os.path.basename(x)) for x in filelist]
584 5
    return joinedfiles
585
586
587 5
def kernchecker_prep(kernlist):
588
    """
589
    Prepare output from kernel list.
590
591
    :param kernlist: List of kernel branches.
592
    :type kernlist: list(str)
593
    """
594 5
    splitkerns = [x.split("/") for x in kernlist]
595 5
    platforms = list({x[0] for x in splitkerns})
596 5
    kerndict = kernchecker_dict(splitkerns, platforms)
597 5
    return kerndict
598
599
600 5
def kernchecker_dict(splitkerns, platforms):
601
    """
602
    Prepare results dictionary.
603
604
    :param splitkerns: Split kernel branches.
605
    :type splitkerns: list(str)
606
607
    :param platforms: List of platform dicts.
608
    :type platforms: list(dict)
609
    """
610 5
    kerndict = {x: [] for x in platforms}
611 5
    for kernel in splitkerns:
612 5
        kerndict[kernel[0]].append("\t{0}".format(kernel[1]))
613 5
    return kerndict
614
615
616 5
def tclloader_prep(loaderfile, directory=False):
617
    """
618
    Prepare directory name and OS version.
619
620
    :param loaderfile: Path to input file/folder.
621
    :type loaderfile: str
622
623
    :param directory: If the input file is a folder. Default is False.
624
    :type directory: bool
625
    """
626 5
    loaderdir = loaderfile if directory else loaderfile.replace(".zip", "")
627 5
    osver = loaderdir.split("-")[-1]
628 5
    return loaderdir, osver
629
630
631 5
def tclloader_filename(loaderdir, osver, loadername=None):
632
    """
633
    Prepare platform and filename.
634
635
    :param loaderdir: Path to input folder.
636
    :type loaderdir: str
637
638
    :param osver: OS version.
639
    :type osver: str
640
641
    :param loadername: Name of final autoloader. Default is auto-generated.
642
    :type loadername: str
643
    """
644 5
    platform = os.listdir(os.path.join(loaderdir, "target", "product"))[0]
645 5
    if loadername is None:
646 5
        loadername = "{0}_autoloader_user-all-{1}".format(platform, osver)
647 5
    return loadername, platform
648
649
650 5
def tcl_download(downloadurl, filename, filesize, filehash, verify=True):
651
    """
652
    Download autoloader file, rename, and verify.
653
654
    :param downloadurl: Download URL.
655
    :type downloadurl: str
656
657
    :param filename: Name of autoloader file.
658
    :type filename: str
659
660
    :param filesize: Size of autoloader file.
661
    :type filesize: str
662
663
    :param filehash: SHA-1 hash of autoloader file.
664
    :type filehash: str
665
666
    :param verify: Whether to verify the file after downloading. Default is True.
667
    :type verify: bool
668
    """
669 5
    print("FILENAME: {0}".format(filename))
670 5
    print("LENGTH: {0}".format(utilities.fsizer(filesize)))
671 5
    networkutils.download(downloadurl)
672 5
    print("DOWNLOAD COMPLETE")
673 5
    os.rename(downloadurl.split("/")[-1], filename)
674 5
    if verify:
675 5
        method = hashutils.get_engine("sha1")
676 5
        shahash = hashutils.hashlib_hash(filename, method)
677 5
        if shahash == filehash:
678 5
            print("HASH CHECK OK")
679
        else:
680 5
            print(shahash)
681 5
            print("HASH FAILED!")
682
683
684 5
def tcl_prd_scan(curef, download=False, mode=4, fvver="AAA000", original=True, export=False, verify=True):
685
    """
686
    Scan one PRD and produce download URL and filename.
687
688
    :param curef: PRD of the phone variant to check.
689
    :type curef: str
690
691
    :param download: If we'll download the file that this returns. Default is False.
692
    :type download: bool
693
694
    :param mode: 4 if downloading autoloaders, 2 if downloading OTA deltas.
695
    :type mode: int
696
697
    :param fvver: Initial software version, must be specific if downloading OTA deltas.
698
    :type fvver: str
699
700
    :param original: If we'll download the file with its original filename. Default is True.
701
    :type original: bool
702
703
    :param export: Whether to export XML response to file. Default is False.
704
    :type export: bool
705
706
    :param verify: Whether to verify the file after downloading. Default is True.
707
    :type verify: bool
708
    """
709 5
    sess = requests.Session()
710 5
    ctext = networkutils.tcl_check(curef, sess, mode, fvver, export)
711 5
    if ctext is None:
712 5
        raise SystemExit
713 5
    tvver, firmwareid, filename, filesize, filehash = xmlutils.parse_tcl_check(ctext)
714 5
    salt = networkutils.tcl_salt()
715 5
    vkhsh = networkutils.vkhash(curef, tvver, firmwareid, salt, mode, fvver)
716 5
    updatetext = networkutils.tcl_download_request(curef, tvver, firmwareid, salt, vkhsh, sess, mode, fvver, export)
717 5
    downloadurl, encslave = xmlutils.parse_tcl_download_request(updatetext)
718 5
    statcode = networkutils.getcode(downloadurl, sess)
719 5
    filename = tcl_delta_filename(curef, fvver, tvver, filename, original)
720 5
    tcl_prd_print(tvver, downloadurl, filename, statcode, encslave, sess)
721 5
    if statcode == 200 and download:
722 5
        tcl_download(downloadurl, filename, filesize, filehash, verify)
723
724
725 5
def tcl_delta_filename(curef, fvver, tvver, filename, original=True):
726
    """
727
    Generate compatible filenames for deltas, if needed.
728
729
    :param curef: PRD of the phone variant to check.
730
    :type curef: str
731
732
    :param fvver: Initial software version.
733
    :type fvver: str
734
735
    :param tvver: Target software version.
736
    :type tvver: str
737
738
    :param filename: File name from download URL, passed through if not changing filename.
739
    :type filename: str
740
741
    :param original: If we'll download the file with its original filename. Default is True.
742
    :type original: bool
743
    """
744 5
    if not original:
745 5
        prdver = curef.split("-")[1]
746 5
        filename = "JSU_PRD-{0}-{1}to{2}.zip".format(prdver, fvver, tvver)
747 5
    return filename
748
749
750 5
def tcl_prd_print(tvver, downloadurl, filename, statcode, encslave, session):
751
    """
752
    Print output from PRD scanning.
753
754
    :param tvver: Target software version.
755
    :type tvver: str
756
757
    :param downloadurl: File to download.
758
    :type downloadurl: str
759
760
    :param filename: File name from download URL.
761
    :type filename: str
762
763
    :param statcode: Status code of download URL.
764
    :type statcode: int
765
766
    :param encslave: Server hosting header script.
767
    :type encslave: str
768
769
    :param session: Session object.
770
    :type session: requests.Session
771
    """
772 5
    print("OS: {0}".format(tvver))
773 5
    print("{0}: HTTP {1}".format(filename, statcode))
774 5
    print(downloadurl)
775 5
    if encslave is not None:
776 5
        address = "/{0}".format(downloadurl.split("/", 3)[3:][0])
777 5
        print("CHECKING HEADER...")
778 5
        sentinel = networkutils.encrypt_header(address, encslave, session)
779 5
        if sentinel is not None:
780 5
            print(sentinel)
781
782
783 5
def tcl_prep_otaver(ota=None):
784
    """
785
    Prepare variables for OTA versus full check.
786
787
    :param ota: The starting version if OTA, None if not. Default is None.
788
    :type ota: str
789
    """
790 5
    if ota is not None:
791 5
        mode = 2
792 5
        fvver = ota
793
    else:
794 5
        mode = 4
795 5
        fvver = "AAA000"
796 5
    return mode, fvver
797
798
799 5
def tcl_delta_remote(curef):
800
    """
801
    Prepare remote version for delta scanning.
802
803
    :param curef: PRD of the phone variant to check.
804
    :type curef: str
805
    """
806 5
    remotedict = networkutils.remote_prd_info()
807 5
    fvver = remotedict.get(curef, "AAA000")
808 5
    if fvver == "AAA000":
809 5
        print("NO REMOTE VERSION FOUND!")
810 5
        raise SystemExit
811 5
    return fvver
812
813
814 5
def tcl_mainscan_preamble(ota=None):
815
    """
816
    Prepare preamble for TCL scanning.
817
818
    :param ota: The starting version if OTA, None if not. Default is None.
819
    :type ota: str
820
    """
821 5
    argutils.slim_preamble("TCLSCAN")
822 5
    if ota is not None:
823 5
        print("PRDs with OTA from OS {0}".format(ota.upper()))
824
825
826 5
def tcl_mainscan_printer(curef, tvver, ota=None):
827
    """
828
    Print output of TCL scanning.
829
830
    :param curef: PRD of the phone variant to check.
831
    :type curef: str
832
833
    :param tvver: Target software version.
834
    :type tvver: str
835
836
    :param ota: The starting version if OTA, None if not. Default is None.
837
    :type ota: str
838
    """
839 5
    if ota is not None:
840 5
        print("{0}: {2} to {1}".format(curef, tvver, ota.upper()))
841
    else:
842 5
        print("{0}: {1}".format(curef, tvver))
843
844
845 5
def tcl_findprd_prepd_start(prddict):
846
    """
847
    Collect list of PRD entries.
848
849
    :param prddict: Device:PRD dictionary.
850
    :type prddict: dict(str: list)
851
    """
852 5
    prda = []
853 5
    for item in prddict.values():
854 5
        prda.extend(item)
855 5
    return prda
856
857
858 5
def tcl_findprd_prepd_middle(prda):
859
    """
860
    Convert PRD entries to list of center:end entries.
861
862
    :param prda: List of PRD-xxxxx-yyy entries.
863
    :type prda: list(str)
864
    """
865 5
    prds = [x.split(" ")[0].replace("PRD-", "").replace("APBI-PRD", "").replace("-", "") for x in prda]
866 5
    prdx = list({x[0:5]: x[5:]} for x in prds)
867 5
    return prdx
868
869
870 5
def tcl_findprd_prepd_end(prdx):
871
    """
872
    Convert list of center:end entries to final center:[ends] dict.
873
874
    :param prdx: List of center:end dict entries.
875
    :type prdx: list(dict(str: str))
876
    """
877 5
    prdf = collections.defaultdict(list)
878 5
    for prdc in prdx:
879 5
        for key, value in prdc.items():
880 5
            prdf[key].append(value)
881 5
    return prdf
882
883
884 5
def tcl_findprd_prepdict(prddict):
885
    """
886
    Prepare dict of center:[ends] entries.
887
888
    :param prddict: Device:PRD dictionary.
889
    :type prddict: dict(str: list)
890
    """
891 5
    prda = tcl_findprd_prepd_start(prddict)
892 5
    prdx = tcl_findprd_prepd_middle(prda)
893 5
    prdfinal = tcl_findprd_prepd_end(prdx)
894 5
    return prdfinal
895
896
897 5
def tcl_findprd_checkfilter(prddict, tocheck=None):
898
    """
899
    Filter PRD dict if needed.
900
901
    :param prddict: PRD center:[ends] dictionary.
902
    :type prddict: collections.defaultdict(str: list)
903
904
    :param tocheck: Specific PRD(s) to check, None if all will be checked. Default is None.
905
    :type tocheck: list(str)
906
    """
907 5
    prddict2 = prddict
908 5
    if tocheck is not None:
909 5
        prddict2 = collections.defaultdict(list)
910 5
        for toch in tocheck:
911 5
            toch = toch.replace("PRD-", "").replace("APBI-PRD", "")
912 5
            prddict2[toch] = prddict[toch]
913 5
    return prddict2
914
915
916 5
def tcl_findprd_centerscan(center, prddict, session, floor=0, ceiling=999, export=False, noprefix=False, key2mode=False):
917
    """
918
    Individual scanning for the center of a PRD.
919
920
    :param center: PRD-center-end.
921
    :type center: str
922
923
    :param prddict: PRD center:[ends] dictionary.
924
    :type prddict: collections.defaultdict(str: list)
925
926
    :param session: Session object.
927
    :type session: requests.Session
928
929
    :param floor: When to start. Default is 0.
930
    :type floor: int
931
932
    :param ceiling: When to stop. Default is 999.
933
    :type ceiling: int
934
935
    :param export: Whether to export XML response to file. Default is False.
936
    :type export: bool
937
938
    :param noprefix: Whether to skip adding "PRD-" prefix. Default is False.
939
    :type noprefix: bool
940
941
    :param key2mode: Whether to use new-style prefix. Default is False.
942
    :type key2mode: bool
943
    """
944 5
    tails = [int(i) for i in prddict[center]]
945 5
    safes = [g for g in range(floor, ceiling) if g not in tails]
946 5
    print("SCANNING ROOT: {0}{1}".format(center, " "*8))
947 5
    tcl_findprd_safescan(safes, center, session, export, noprefix, key2mode)
948
949
950 5
def tcl_findprd_prepcuref(center, tail, noprefix=False, key2mode=False):
951
    """
952
    Prepare candidate PRD.
953
954
    :param center: PRD-center-tail.
955
    :type center: str
956
957
    :param tail: PRD-center-tail.
958
    :type tail: int
959
960
    :param noprefix: Whether to skip adding "PRD-" prefix. Default is False.
961
    :type noprefix: bool
962
963
    :param key2mode: Whether to use new-style prefix. Default is False.
964
    :type key2mode: bool
965
    """
966 5
    if key2mode:
967 5
        curef = "APBI-PRD{0}{1:03}".format(center, tail)
968
    else:
969 5
        prefix = "" if noprefix else "PRD-"
970 5
        curef = "{2}{0}-{1:03}".format(center, tail, prefix)
971 5
    return curef
972
973
974 5
def tcl_findprd_safescan(safes, center, session, export=False, noprefix=False, key2mode=False):
975
    """
976
    Scan for PRDs known not to be in database.
977
978
    :param safes: List of ends within given range that aren't in database.
979
    :type safes: list(int)
980
981
    :param center: PRD-center-end.
982
    :type center: str
983
984
    :param session: Session object.
985
    :type session: requests.Session
986
987
    :param export: Whether to export XML response to file. Default is False.
988
    :type export: bool
989
990
    :param noprefix: Whether to skip adding "PRD-" prefix. Default is False.
991
    :type noprefix: bool
992
993
    :param key2mode: Whether to use new-style prefix. Default is False.
994
    :type key2mode: bool
995
    """
996 5
    for j in safes:
997 5
        curef = tcl_findprd_prepcuref(center, j, noprefix, key2mode)
998 5
        print("NOW SCANNING: {0}".format(curef), end="\r")
999 5
        checktext = networkutils.tcl_check(curef, session, export)
1000 5
        if checktext is None:
1001 5
            continue
1002
        else:
1003 5
            tcl_findprd_safehandle(curef, checktext)
1004
1005
1006 5
def tcl_findprd_safehandle(curef, checktext):
1007
    """
1008
    Parse API output and print the relevant bits.
1009
1010
    :param curef: PRD of the phone variant to check.
1011
    :type curef: str
1012
1013
    :param checktext: The XML formatted data returned from the first stage API check.
1014
    :type checktext: str
1015
    """
1016 5
    tvver, firmwareid, filename, fsize, fhash = xmlutils.parse_tcl_check(checktext)
1017 5
    del firmwareid, filename, fsize, fhash
1018 5
    tvver2 = "{0}{1}".format(tvver, " "*8)
1019 5
    tcl_mainscan_printer(curef, tvver2)
1020
1021
1022 5
def tcl_findprd(prddict, floor=0, ceiling=999, export=False, noprefix=False, key2mode=False):
1023
    """
1024
    Check for new PRDs based on PRD database.
1025
1026
    :param prddict: PRD center:[ends] dictionary.
1027
    :type prddict: collections.defaultdict(str: list)
1028
1029
    :param floor: When to start. Default is 0.
1030
    :type floor: int
1031
1032
    :param ceiling: When to stop. Default is 999.
1033
    :type ceiling: int
1034
1035
    :param export: Whether to export XML response to file. Default is False.
1036
    :type export: bool
1037
1038
    :param noprefix: Whether to skip adding "PRD-" prefix. Default is False.
1039
    :type noprefix: bool
1040
1041
    :param key2mode: Whether to use new-style prefix. Default is False.
1042
    :type key2mode: bool
1043
    """
1044 5
    sess = requests.Session()
1045 5
    for center in sorted(prddict.keys()):
1046 5
        tcl_findprd_centerscan(center, prddict, sess, floor, ceiling, export, noprefix, key2mode)
1047
1048
1049 5
def linkgen_sdk_dicter(indict, origtext, newtext):
1050
    """
1051
    Prepare SDK radio/OS dictionaries.
1052
1053
    :param indict: Dictionary of radio and OS pairs.
1054
    :type: dict(str:str)
1055
1056
    :param origtext: String in indict's values that must be replaced.
1057
    :type origtext: str
1058
1059
    :param newtext: What to replace origtext with.
1060
    :type newtext: str
1061
    """
1062 5
    return {key: val.replace(origtext, newtext) for key, val in indict.items()}
1063
1064
1065 5
def linkgen_sdk(sdk, oses, cores):
1066
    """
1067
    Generate SDK debrick/core images.
1068
1069
    :param sdk: If we specifically want SDK images. Default is False.
1070
    :type sdk: bool
1071
1072
    :param oses: Dictionary of radio and debrick pairs.
1073
    :type oses: dict(str:str)
1074
1075
    :param cores: Dictionary of radio and core pairs.
1076
    :type cores: dict(str:str)
1077
    """
1078 5
    if sdk:
1079 5
        oses2 = linkgen_sdk_dicter(oses, "factory_sfi", "sdk")
1080 5
        cores2 = linkgen_sdk_dicter(cores, "factory_sfi", "sdk")
1081 5
        oses = linkgen_sdk_dicter(oses2, "verizon_sfi", "sdk")
1082 5
        cores = linkgen_sdk_dicter(cores2, "verizon_sfi", "sdk")
1083 5
    return oses, cores
1084
1085
1086 5
def linkgen(osversion, radioversion=None, softwareversion=None, altsw=None, temp=False, sdk=False):
1087
    """
1088
    Generate debrick/core/radio links for given OS, radio, software release.
1089
1090
    :param osversion: OS version, 10.x.y.zzzz.
1091
    :type osversion: str
1092
1093
    :param radioversion: Radio version, 10.x.y.zzzz. Can be guessed.
1094
    :type radioversion: str
1095
1096
    :param softwareversion: Software version, 10.x.y.zzzz. Can be guessed.
1097
    :type softwareversion: str
1098
1099
    :param altsw: Radio software release, if not the same as OS.
1100
    :type altsw: str
1101
1102
    :param temp: If file we write to is temporary. Default is False.
1103
    :type temp: bool
1104
1105
    :param sdk: If we specifically want SDK images. Default is False.
1106
    :type sdk: bool
1107
    """
1108 5
    radioversion = return_radio_version(osversion, radioversion)
1109 5
    softwareversion, swc = return_sw_checked(softwareversion, osversion)
1110 5
    del swc
1111 5
    if altsw is not None:
1112 5
        altsw, aswc = return_radio_sw_checked(altsw, radioversion)
1113 5
        del aswc
1114 5
    baseurl = utilities.create_base_url(softwareversion)
1115 5
    oses, cores, radios = textgenerator.url_gen(osversion, radioversion, softwareversion)
1116 5
    if altsw is not None:
1117 5
        del radios
1118 5
        dbks, cors, radios = textgenerator.url_gen(osversion, radioversion, altsw)
1119 5
        del dbks
1120 5
        del cors
1121 5
    avlty = networkutils.availability(baseurl)
1122 5
    oses, cores = linkgen_sdk(sdk, oses, cores)
1123 5
    prargs = (softwareversion, osversion, radioversion, oses, cores, radios, avlty, False, None, temp, altsw)
1124 5
    lthr = threading.Thread(target=textgenerator.write_links, args=prargs)
1125 5
    lthr.start()
1126
1127
1128 5
def clean_swrel(swrelset):
1129
    """
1130
    Clean a list of software release lookups.
1131
1132
    :param swrelset: List of software releases.
1133
    :type swrelset: set(str)
1134
    """
1135 5
    for i in swrelset:
1136 5
        if i != "SR not in system" and i is not None:
1137 5
            swrelease = i
1138 5
            break
1139
    else:
1140 5
        swrelease = ""
1141 5
    return swrelease
1142
1143
1144 5
def autolookup_logger(record, out):
1145
    """
1146
    Write autolookup results to file.
1147
1148
    :param record: The file to log to.
1149
    :type record: str
1150
1151
    :param out: Output block.
1152
    :type out: str
1153
    """
1154 5
    with open(record, "a") as rec:
1155 5
        rec.write("{0}\n".format(out))
1156
1157
1158 5
def autolookup_printer(out, avail, log=False, quiet=False, record=None):
1159
    """
1160
    Print autolookup results, logging if specified.
1161
1162
    :param out: Output block.
1163
    :type out: str
1164
1165
    :param avail: Availability. Can be "Available" or "Unavailable".
1166
    :type avail: str
1167
1168
    :param log: If we're logging to file.
1169
    :type log: bool
1170
1171
    :param quiet: If we only note available entries.
1172
    :type quiet: bool
1173
1174
    :param record: If we're logging, the file to log to.
1175
    :type record: str
1176
    """
1177 5
    if not quiet:
1178 5
        avail = "Available"  # force things
1179 5
    if avail.lower() == "available":
1180 5
        if log:
1181 5
            lthr = threading.Thread(target=autolookup_logger, args=(record, out))
1182 5
            lthr.start()
1183 5
        print(out)
1184
1185
1186 5
def autolookup_output_sql(osversion, swrelease, avail, sql=False):
1187
    """
1188
    Add OS to SQL database.
1189
1190
    :param osversion: OS version.
1191
    :type osversion: str
1192
1193
    :param swrelease: Software release.
1194
    :type swrelease: str
1195
1196
    :param avail: "Unavailable" or "Available".
1197
    :type avail: str
1198
1199
    :param sql: If we're adding this to our SQL database.
1200
    :type sql: bool
1201
    """
1202 5
    if sql:
1203 5
        sqlutils.prepare_sw_db()
1204 5
        if not sqlutils.check_exists(osversion, swrelease):
1205 5
            sqlutils.insert(osversion, swrelease, avail.lower())
1206
1207
1208 5
def autolookup_output(osversion, swrelease, avail, avpack, sql=False):
1209
    """
1210
    Prepare autolookup block, and add to SQL database.
1211
1212
    :param osversion: OS version.
1213
    :type osversion: str
1214
1215
    :param swrelease: Software release.
1216
    :type swrelease: str
1217
1218
    :param avail: "Unavailable" or "Available".
1219
    :type avail: str
1220
1221
    :param avpack: Availabilities: alpha 1 and 2, beta 1 and 2, production.
1222
    :type avpack: list(str)
1223
1224
    :param sql: If we're adding this to our SQL database.
1225
    :type sql: bool
1226
    """
1227 5
    othr = threading.Thread(target=autolookup_output_sql, args=(osversion, swrelease, avail, sql))
1228 5
    othr.start()
1229 5
    avblok = "[{0}|{1}|{2}|{3}|{4}]".format(*avpack)
1230 5
    out = "OS {0} - SR {1} - {2} - {3}".format(osversion, swrelease, avblok, avail)
1231 5
    return out
1232
1233
1234 5
def clean_barlist(cleanfiles, stoppers):
1235
    """
1236
    Remove certain bars from barlist based on keywords.
1237
1238
    :param cleanfiles: List of files to clean.
1239
    :type cleanfiles: list(str)
1240
1241
    :param stoppers: List of keywords (i.e. bar names) to exclude.
1242
    :type stoppers: list(str)
1243
    """
1244 5
    finals = [link for link in cleanfiles if all(word not in link for word in stoppers)]
1245 5
    return finals
1246
1247
1248 5
def prep_export_cchecker(files, npc, hwid, osv, radv, swv, upgrade=False, forced=None):
1249
    """
1250
    Prepare carrierchecker lookup links to write to file.
1251
1252
    :param files: List of file URLs.
1253
    :type files: list(str)
1254
1255
    :param npc: MCC + MNC (ex. 302220).
1256
    :type npc: int
1257
1258
    :param hwid: Device hardware ID.
1259
    :type hwid: str
1260
1261
    :param osv: OS version.
1262
    :type osv: str
1263
1264
    :param radv: Radio version.
1265
    :type radv: str
1266
1267
    :param swv: Software release.
1268
    :type swv: str
1269
1270
    :param upgrade: Whether or not to use upgrade files. Default is false.
1271
    :type upgrade: bool
1272
1273
    :param forced: Force a software release. None to go for latest.
1274
    :type forced: str
1275
    """
1276 5
    if not upgrade:
1277 5
        newfiles = networkutils.carrier_query(npc, hwid, True, False, forced)
1278 5
        cleanfiles = newfiles[3]
1279
    else:
1280 5
        cleanfiles = files
1281 5
    osurls, coreurls, radiourls = textgenerator.url_gen(osv, radv, swv)
1282 5
    stoppers = ["8960", "8930", "8974", "m5730", "winchester"]
1283 5
    finals = clean_barlist(cleanfiles, stoppers)
1284 5
    return osurls, coreurls, radiourls, finals
1285
1286
1287 5
def export_cchecker(files, npc, hwid, osv, radv, swv, upgrade=False, forced=None):
1288
    """
1289
    Write carrierchecker lookup links to file.
1290
1291
    :param files: List of file URLs.
1292
    :type files: list(str)
1293
1294
    :param npc: MCC + MNC (ex. 302220).
1295
    :type npc: int
1296
1297
    :param hwid: Device hardware ID.
1298
    :type hwid: str
1299
1300
    :param osv: OS version.
1301
    :type osv: str
1302
1303
    :param radv: Radio version.
1304
    :type radv: str
1305
1306
    :param swv: Software release.
1307
    :type swv: str
1308
1309
    :param upgrade: Whether or not to use upgrade files. Default is false.
1310
    :type upgrade: bool
1311
1312
    :param forced: Force a software release. None to go for latest.
1313
    :type forced: str
1314
    """
1315 5
    if files:
1316 5
        osurls, coreurls, radiourls, finals = prep_export_cchecker(files, npc, hwid, osv, radv, swv, upgrade, forced)
1317 5
        textgenerator.write_links(swv, osv, radv, osurls, coreurls, radiourls, True, True, finals)
1318 5
        print("\nFINISHED!!!")
1319
    else:
1320 5
        print("CANNOT EXPORT, NO SOFTWARE RELEASE")
1321
1322
1323 5
def generate_blitz_links(files, osv, radv, swv):
1324
    """
1325
    Generate blitz URLs (i.e. all OS and radio links).
1326
    :param files: List of file URLs.
1327
    :type files: list(str)
1328
1329
    :param osv: OS version.
1330
    :type osv: str
1331
1332
    :param radv: Radio version.
1333
    :type radv: str
1334
1335
    :param swv: Software release.
1336
    :type swv: str
1337
    """
1338 5
    coreurls = [
1339
        utilities.create_bar_url(swv, "winchester.factory_sfi", osv),
1340
        utilities.create_bar_url(swv, "qc8960.factory_sfi", osv),
1341
        utilities.create_bar_url(swv, "qc8960.factory_sfi", osv),
1342
        utilities.create_bar_url(swv, "qc8960.factory_sfi_hybrid_qc8974", osv)
1343
    ]
1344 5
    radiourls = [
1345
        utilities.create_bar_url(swv, "m5730", radv),
1346
        utilities.create_bar_url(swv, "qc8960", radv),
1347
        utilities.create_bar_url(swv, "qc8960.wtr", radv),
1348
        utilities.create_bar_url(swv, "qc8960.wtr5", radv),
1349
        utilities.create_bar_url(swv, "qc8930.wtr5", radv),
1350
        utilities.create_bar_url(swv, "qc8974.wtr2", radv)
1351
    ]
1352 5
    return files + coreurls + radiourls
1353
1354
1355 5
def package_blitz(bardir, swv):
1356
    """
1357
    Package and verify a blitz package.
1358
1359
    :param bardir: Path to folder containing bar files.
1360
    :type bardir: str
1361
1362
    :param swv: Software version.
1363
    :type swv: str
1364
    """
1365 5
    print("\nCREATING BLITZ...")
1366 5
    barutils.create_blitz(bardir, swv)
1367 5
    print("\nTESTING BLITZ...")
1368 5
    zipver = archiveutils.zip_verify("Blitz-{0}.zip".format(swv))
1369 5
    if not zipver:
1370 5
        print("BLITZ FILE IS BROKEN")
1371 5
        raise SystemExit
1372
    else:
1373 5
        shutil.rmtree(bardir)
1374
1375
1376 5
def questionnaire_device(message=None):
1377
    """
1378
    Get device from questionnaire.
1379
    """
1380 5
    message = "DEVICE (XXX100-#): " if message is None else message
1381 5
    device = input(message)
1382 5
    if not device:
1383 5
        print("NO DEVICE SPECIFIED!")
1384 5
        decorators.enter_to_exit(True)
1385 5
        if not getattr(sys, 'frozen', False):
1386 5
            raise SystemExit
1387 5
    return device
1388
1389
1390 5
def verify_gpg_credentials():
1391
    """
1392
    Read GPG key/pass from file, verify if incomplete.
1393
    """
1394 5
    gpgkey, gpgpass = gpgutils.gpg_config_loader()
1395 5
    if gpgkey is None or gpgpass is None:
1396 5
        print("NO PGP KEY/PASS FOUND")
1397 5
        cont = utilities.i2b("CONTINUE (Y/N)?: ")
1398 5
        if cont:
1399 5
            gpgkey = verify_gpg_key(gpgkey)
1400 5
            gpgpass, writebool = verify_gpg_pass(gpgpass)
1401 5
            gpgpass2 = gpgpass if writebool else None
1402 5
            gpgutils.gpg_config_writer(gpgkey, gpgpass2)
1403
        else:
1404 5
            gpgkey = None
1405 5
    return gpgkey, gpgpass
1406
1407
1408 5
def verify_gpg_key(gpgkey=None):
1409
    """
1410
    Verify GPG key.
1411
1412
    :param gpgkey: Key, use None to take from input.
1413
    :type gpgkey: str
1414
    """
1415 5
    if gpgkey is None:
1416 5
        gpgkey = input("PGP KEY (0x12345678): ")
1417 5
        if not gpgkey.startswith("0x"):
1418 5
            gpgkey = "0x{0}".format(gpgkey)   # add preceding 0x
1419 5
    return gpgkey
1420
1421
1422 5
def verify_gpg_pass(gpgpass=None):
1423
    """
1424
    Verify GPG passphrase.
1425
1426
    :param gpgpass: Passphrase, use None to take from input.
1427
    :type gpgpass: str
1428
    """
1429 5
    if gpgpass is None:
1430 5
        gpgpass = getpass.getpass(prompt="PGP PASSPHRASE: ")
1431 5
        writebool = utilities.i2b("SAVE PASSPHRASE (Y/N)?:")
1432
    else:
1433 5
        writebool = False
1434 5
    return gpgpass, writebool
1435
1436
1437 5
def bulk_hash(dirs, compressed=True, deleted=True, radios=True, hashdict=None):
1438
    """
1439
    Hash files in several folders based on flags.
1440
1441
    :param dirs: Folders: [OS_bars, radio_bars, OS_exes, radio_exes, OS_zips, radio_zips]
1442
    :type dirs: list(str)
1443
1444
    :param compressed: Whether to hash compressed files. True by default.
1445
    :type compressed: bool
1446
1447
    :param deleted: Whether to delete uncompressed files. True by default.
1448
    :type deleted: bool
1449
1450
    :param radios: Whether to hash radio autoloaders. True by default.
1451
    :type radios: bool
1452
1453
    :param hashdict: Dictionary of hash rules, in ~\bbarchivist.ini.
1454
    :type hashdict: dict({str: bool})
1455
    """
1456 5
    print("HASHING LOADERS...")
1457 5
    defargs = utilities.def_args(dirs)
1458 5
    utilities.cond_check(hashutils.verifier, defargs, [hashdict], radios, compressed, deleted)
1459
1460
1461 5
def bulk_verify(dirs, compressed=True, deleted=True, radios=True):
1462
    """
1463
    Verify files in several folders based on flags.
1464
1465
    :param dirs: Folders: [OS_bars, radio_bars, OS_exes, radio_exes, OS_zips, radio_zips]
1466
    :type dirs: list(str)
1467
1468
    :param compressed: Whether to hash compressed files. True by default.
1469
    :type compressed: bool
1470
1471
    :param deleted: Whether to delete uncompressed files. True by default.
1472
    :type deleted: bool
1473
1474
    :param radios: Whether to hash radio autoloaders. True by default.
1475
    :type radios: bool
1476
    """
1477 5
    gpgkey, gpgpass = verify_gpg_credentials()
1478 5
    if gpgpass is not None and gpgkey is not None:
1479 5
        print("VERIFYING LOADERS...")
1480 5
        print("KEY: {0}".format(gpgkey))
1481 5
        restargs = [gpgkey, gpgpass, True]
1482 5
        defargs = utilities.def_args(dirs)
1483 5
        utilities.cond_check(gpgutils.gpgrunner, defargs, restargs, radios, compressed, deleted)
1484
1485
1486 5
def enn_ayy(quant):
1487
    """
1488
    Cheeky way to put a N/A placeholder for a string.
1489
1490
    :param quant: What to check if it's None.
1491
    :type quant: str
1492
    """
1493 5
    return "N/A" if quant is None else quant
1494
1495
1496 5
def generate_workfolder(folder=None):
1497
    """
1498
    Check if a folder exists, make it if it doesn't, set it to home if None.
1499
1500
    :param folder: Folder to check.
1501
    :type folder: str
1502
    """
1503 5
    folder = utilities.dirhandler(folder, os.getcwd())
1504 5
    if folder is not None and not os.path.exists(folder):
1505 5
        os.makedirs(folder)
1506 5
    return folder
1507
1508
1509 5
def info_header(afile, osver, radio=None, software=None, device=None):
1510
    """
1511
    Write header for info file.
1512
1513
    :param afile: Open file to write to.
1514
    :type afile: File object
1515
1516
    :param osver: OS version, required for both types.
1517
    :type osver: str
1518
1519
    :param radio: Radio version, required for QNX.
1520
    :type radio: str
1521
1522
    :param software: Software release, required for QNX.
1523
    :type software: str
1524
1525
    :param device: Device type, required for Android.
1526
    :type device: str
1527
    """
1528 5
    afile.write("OS: {0}\n".format(osver))
1529 5
    if device:
1530 5
        afile.write("Device: {0}\n".format(enn_ayy(device)))
1531
    else:
1532 5
        afile.write("Radio: {0}\n".format(enn_ayy(radio)))
1533 5
        afile.write("Software: {0}\n".format(enn_ayy(software)))
1534 5
    afile.write("{0}\n".format("~"*40))
1535
1536
1537 5
def prep_info(filepath, osver, device=None):
1538
    """
1539
    Prepare file list for new-style info file.
1540
1541
    :param filepath: Path to folder to analyze.
1542
    :type filepath: str
1543
1544
    :param osver: OS version, required for both types.
1545
    :type osver: str
1546
1547
    :param device: Device type, required for Android.
1548
    :type device: str
1549
    """
1550 5
    fileext = ".zip" if device else ".7z"
1551 5
    files = os.listdir(filepath)
1552 5
    absfiles = [os.path.join(filepath, x) for x in files if x.endswith((fileext, ".exe"))]
1553 5
    fname = os.path.join(filepath, "!{0}_OSINFO!.txt".format(osver))
1554 5
    return fname, absfiles
1555
1556
1557 5
def make_info(filepath, osver, radio=None, software=None, device=None):
1558
    """
1559
    Create a new-style info (names, sizes and hashes) file.
1560
1561
    :param filepath: Path to folder to analyze.
1562
    :type filepath: str
1563
1564
    :param osver: OS version, required for both types.
1565
    :type osver: str
1566
1567
    :param radio: Radio version, required for QNX.
1568
    :type radio: str
1569
1570
    :param software: Software release, required for QNX.
1571
    :type software: str
1572
1573
    :param device: Device type, required for Android.
1574
    :type device: str
1575
    """
1576 5
    fname, absfiles = prep_info(filepath, osver, device)
1577 5
    with open(fname, "w") as afile:
1578 5
        info_header(afile, osver, radio, software, device)
1579 5
        for indx, file in enumerate(absfiles):
1580 5
            write_info(file, indx, len(absfiles), afile)
1581
1582
1583 5
def write_info(infile, index, filecount, outfile):
1584
    """
1585
    Write a new-style info (names, sizes and hashes) file.
1586
1587
    :param infile: Path to file whose name, size and hash are to be written.
1588
    :type infile: str
1589
1590
    :param index: Which file index out of the list of files we're writing.
1591
    :type index: int
1592
1593
    :param filecount: Total number of files we're to write; for excluding terminal newline.
1594
    :type filecount: int
1595
1596
    :param outfile: Open (!!!) file handle. Output file.
1597
    :type outfile: str
1598
    """
1599 5
    fsize = os.stat(infile).st_size
1600 5
    outfile.write("File: {0}\n".format(os.path.basename(infile)))
1601 5
    outfile.write("\tSize: {0} ({1})\n".format(fsize, utilities.fsizer(fsize)))
1602 5
    outfile.write("\tHashes:\n")
1603 5
    outfile.write("\t\tMD5: {0}\n".format(hashutils.hashlib_hash(infile, hashlib.md5()).upper()))
1604 5
    outfile.write("\t\tSHA1: {0}\n".format(hashutils.hashlib_hash(infile, hashlib.sha1()).upper()))
1605 5
    outfile.write("\t\tSHA256: {0}\n".format(hashutils.hashlib_hash(infile, hashlib.sha256()).upper()))
1606 5
    outfile.write("\t\tSHA512: {0}\n".format(hashutils.hashlib_hash(infile, hashlib.sha512()).upper()))
1607 5
    if index != filecount - 1:
1608 5
        outfile.write("\n")
1609
1610
1611 5
def bulk_info(dirs, osv, compressed=True, deleted=True, radios=True, rad=None, swv=None, dev=None):
1612
    """
1613
    Generate info files in several folders based on flags.
1614
1615
    :param dirs: Folders: [OS_bars, radio_bars, OS_exes, radio_exes, OS_zips, radio_zips]
1616
    :type dirs: list(str)
1617
1618
    :param osver: OS version, required for both types.
1619
    :type osver: str
1620
1621
    :param compressed: Whether to hash compressed files. True by default.
1622
    :type compressed: bool
1623
1624
    :param deleted: Whether to delete uncompressed files. True by default.
1625
    :type deleted: bool
1626
1627
    :param radios: Whether to hash radio autoloaders. True by default.
1628
    :type radios: bool
1629
1630
    :param rad: Radio version, required for QNX.
1631
    :type rad: str
1632
1633
    :param swv: Software release, required for QNX.
1634
    :type swv: str
1635
1636
    :param dev: Device type, required for Android.
1637
    :type dev: str
1638
    """
1639 5
    print("GENERATING INFO FILES...")
1640 5
    restargs = [osv, rad, swv, dev]
1641
    utilities.cond_check(make_info, utilities.def_args(dirs), restargs, radios, compressed, deleted)
1642