Completed
Push — master ( ec2970...c0a25d )
by John
03:48
created

tcl_findprd_safescan()   B

Complexity

Conditions 3

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3

Importance

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