Completed
Push — master ( a42aad...9def7b )
by John
03:23
created

tcl_delta_remote()   A

Complexity

Conditions 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

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