Completed
Push — master ( f5d9f6...12103c )
by John
03:10
created

test_bar_files()   A

Complexity

Conditions 3

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
dl 0
loc 20
rs 9.4285
c 0
b 0
f 0
ccs 10
cts 10
cp 1
crap 3
1
#!/usr/bin/env python3
2 4
"""This module contains various utilities for the scripts folder."""
3
4 4
import os  # path work
5 4
import getpass  # invisible password
6 4
import argparse  # generic parser
7 4
import hashlib  # hashes
8 4
import sys  # getattr
9 4
import shutil  # folder removal
10 4
import subprocess  # running cfp/cap
11 4
import glob  # file lookup
12 4
import threading  # run stuff in background
13 4
import requests  # session
14 4
from bbarchivist import utilities  # little things
15 4
from bbarchivist import decorators  # decorating functions
16 4
from bbarchivist import barutils  # file system work
17 4
from bbarchivist import archiveutils  # archive support
18 4
from bbarchivist import bbconstants  # constants
19 4
from bbarchivist import gpgutils  # gpg
20 4
from bbarchivist import hashutils  # file hashes
21 4
from bbarchivist import networkutils  # network tools
22 4
from bbarchivist import textgenerator  # writing text to file
23 4
from bbarchivist import smtputils  # email
24 4
from bbarchivist import sqlutils  # sql
25
26 4
__author__ = "Thurask"
27 4
__license__ = "WTFPL v2"
28 4
__copyright__ = "Copyright 2015-2016 Thurask"
29
30
31 4
def shortversion():
32
    """
33
    Get short app version (Git tag).
34
    """
35 4
    if not getattr(sys, 'frozen', False):
36 4
        ver = bbconstants.VERSION
37
    else:
38 4
        verfile = glob.glob(os.path.join(os.getcwd(), "version.txt"))[0]
39 4
        with open(verfile) as afile:
40 4
            ver = afile.read()
41 4
    return ver
42
43
44 4
def longversion():
45
    """
46
    Get long app version (Git tag + commits + hash).
47
    """
48 4
    if not getattr(sys, 'frozen', False):
49 4
        ver = (bbconstants.LONGVERSION, bbconstants.COMMITDATE)
50
    else:
51 4
        verfile = glob.glob(os.path.join(os.getcwd(), "longversion.txt"))[0]
52 4
        with open(verfile) as afile:
53 4
            ver = afile.read().split("\n")
54 4
    return ver
55
56
57 4
def default_parser(name=None, desc=None, flags=None, vers=None):
58
    """
59
    A generic form of argparse's ArgumentParser.
60
61
    :param name: App name.
62
    :type name: str
63
64
    :param desc: App description.
65
    :type desc: str
66
67
    :param flags: Tuple of sections to add.
68
    :type flags: tuple(str)
69
70
    :param vers: Versions: [git commit hash, git commit date]
71
    :param vers: list(str)
72
    """
73 4
    if vers is None:
74 4
        vers = longversion()
75 4
    parser = argparse.ArgumentParser(
76
        prog=name,
77
        description=desc,
78
        epilog="https://github.com/thurask/bbarchivist")
79 4
    parser.add_argument(
80
        "-v",
81
        "--version",
82
        action="version",
83
        version="{0} {1} committed {2}".format(parser.prog, vers[0], vers[1]))
84 4
    if flags is not None:
85 4
        if "folder" in flags:
86 4
            parser.add_argument(
87
                "-f",
88
                "--folder",
89
                dest="folder",
90
                help="Working folder",
91
                default=None,
92
                metavar="DIR",
93
                type=utilities.file_exists)
94 4
        if "osr" in flags:
95 4
            parser.add_argument(
96
                "os",
97
                help="OS version")
98 4
            parser.add_argument(
99
                "radio",
100
                help="Radio version, 10.x.y.zzzz",
101
                nargs="?",
102
                default=None)
103 4
            parser.add_argument(
104
                "swrelease",
105
                help="Software version, 10.x.y.zzzz",
106
                nargs="?",
107
                default=None)
108 4
    return parser
109
110
111 4
def generic_windows_shim(scriptname, scriptdesc, target, version):
112
    """
113
    Generic CFP/CAP runner; Windows only.
114
115
    :param scriptname: Script name, 'bb-something'.
116
    :type scriptname: str
117
118
    :param scriptdesc: Script description, i.e. scriptname -h.
119
    :type scriptdesc: str
120
121
    :param target: Path to file to execute.
122
    :type target: str
123
124
    :param version: Version of target.
125
    :type version: str
126
    """
127 4
    parser = default_parser(scriptname, scriptdesc)
128 4
    capver = "|{0}".format(version)
129 4
    parser = external_version(parser, capver)
130 4
    parser.parse_known_args(sys.argv[1:])
131 4
    if utilities.is_windows():
132 4
        subprocess.call([target] + sys.argv[1:])
133
    else:
134 4
        print("Sorry, Windows only.")
135
136
137 4
def external_version(parser, addition):
138
    """
139
    Modify the version string of argparse.ArgumentParser, adding something.
140
141
    :param parser: Parser to modify.
142
    :type parser: argparse.ArgumentParser
143
144
    :param addition: What to add.
145
    :type addition: str
146
    """
147 4
    verarg = [arg for arg in parser._actions if isinstance(arg, argparse._VersionAction)][0]
148 4
    verarg.version = "{1}{0}".format(addition, verarg.version)
149 4
    return parser
150
151
152 4
def return_radio_version(osversion, radioversion=None):
153
    """
154
    Increment radio version, if need be.
155
156
    :param osversion: OS version.
157
    :type osversion: str
158
159
    :param radioversion: Radio version, None if incremented.
160
    :type radioversion: str
161
    """
162 4
    if radioversion is None:
163 4
        radioversion = utilities.increment(osversion, 1)
164 4
    return radioversion
165
166
167 4
def sw_check_contingency(softwareversion):
168
    """
169
    Ask in the event software release isn't found.
170
171
    :param softwareversion: Software release version.
172
    :type softwareversion: str
173
    """
174 4
    if softwareversion == "SR not in system":
175 4
        print("SOFTWARE RELEASE NOT FOUND")
176 4
        cont = utilities.s2b(input("INPUT MANUALLY? Y/N: "))
177 4
        if cont:
178 4
            softwareversion = input("SOFTWARE RELEASE: ")
179 4
            swchecked = False
180
        else:
181 4
            print("\nEXITING...")
182 4
            raise SystemExit  # bye bye
183
    else:
184 4
        swchecked = True
185 4
    return softwareversion, swchecked
186
187
188 4
def return_sw_checked(softwareversion, osversion):
189
    """
190
    Check software existence, return boolean.
191
192
    :param softwareversion: Software release version.
193
    :type softwareversion: str
194
195
    :param osversion: OS version.
196
    :type osversion: str
197
    """
198 4
    if softwareversion is None:
199 4
        serv = bbconstants.SERVERS["p"]
200 4
        softwareversion = networkutils.sr_lookup(osversion, serv)
201 4
        softwareversion, swchecked = sw_check_contingency(softwareversion)
202
    else:
203 4
        swchecked = True
204 4
    return softwareversion, swchecked
205
206
207 4
def return_radio_sw_checked(altsw, radioversion):
208
    """
209
    Check radio software existence, return boolean.
210
211
    :param altsw: Software release version.
212
    :type altsw: str
213
214
    :param radioversion: Radio version.
215
    :type radioversion: str
216
    """
217 4
    if altsw == "checkme":
218 4
        serv = bbconstants.SERVERS["p"]
219 4
        testos = utilities.increment(radioversion, -1)
220 4
        altsw = networkutils.sr_lookup(testos, serv)
221 4
        altsw, altchecked = sw_check_contingency(altsw)
222
    else:
223 4
        altchecked = True
224 4
    return altsw, altchecked
225
226
227 4
def check_sw(baseurl, softwareversion, swchecked, altsw=False):
228
    """
229
    Check existence of software release.
230
231
    :param baseurl: Base URL (from http to hashed SW release).
232
    :type basurl: str
233
234
    :param softwareversion: Software release.
235
    :type softwareversion: str
236
237
    :param swchecked: If we checked the sw release already.
238
    :type swchecked: bool
239
240
    :param altsw: If this is the radio-only release. Default is false.
241
    :type altsw: bool
242
    """
243 4
    message = "CHECKING RADIO SOFTWARE RELEASE..." if altsw else "CHECKING SOFTWARE RELEASE..."
244 4
    print(message)
245 4
    if not swchecked:
246 4
        avlty = networkutils.availability(baseurl)
247 4
        if avlty:
248 4
            print("SOFTWARE RELEASE {0} EXISTS".format(softwareversion))
249
        else:
250 4
            print("SOFTWARE RELEASE {0} NOT FOUND".format(softwareversion))
251 4
            cont = utilities.s2b(input("CONTINUE? Y/N: "))
252 4
            if not cont:
253 4
                print("\nEXITING...")
254 4
                raise SystemExit
255
    else:
256 4
        print("SOFTWARE RELEASE {0} EXISTS".format(softwareversion))
257
258
259 4
def check_radio_sw(alturl, altsw, altchecked):
260
    """
261
    Check existence of radio software release.
262
263
    :param alturl: Radio base URL (from http to hashed SW release).
264
    :type alturl: str
265
266
    :param altsw: Radio software release.
267
    :type altsw: str
268
269
    :param altchecked: If we checked the sw release already.
270
    :type altchecked: bool
271
    """
272 4
    return check_sw(alturl, altsw, altchecked, True)
273
274
275 4
def check_altsw(altcheck=False):
276
    """
277
    Ask for and return alternate software release, if needed.
278
279
    :param altcheck: If we're using an alternate software release.
280
    :type altcheck: bool
281
    """
282 4
    if altcheck:
283 4
        altsw = input("RADIO SOFTWARE RELEASE (PRESS ENTER TO GUESS): ")
284 4
        if not altsw:
285 4
            altsw = "checkme"
286
    else:
287 4
        altsw = None
288 4
    return altsw
289
290
291 4
def check_os_single(osurl, osversion, device):
292
    """
293
    Check existence of single OS link.
294
295
    :param radiourl: Radio URL to check.
296
    :type radiourl: str
297
298
    :param radioversion: Radio version.
299
    :type radioversion: str
300
301
    :param device: Device family.
302
    :type device: int
303
    """
304 4
    osav = networkutils.availability(osurl)
305 4
    if not osav:
306 4
        print("{0} NOT AVAILABLE FOR {1}".format(osversion, bbconstants.DEVICES[device]))
307 4
        cont = utilities.s2b(input("CONTINUE? Y/N: "))
308 4
        if not cont:
309 4
            print("\nEXITING...")
310 4
            raise SystemExit
311
312
313 4
def check_os_bulk(osurls):
314
    """
315
    Check existence of list of OS links.
316
317
    :param osurls: OS URLs to check.
318
    :type osurls: list(str)
319
    """
320 4
    sess = requests.Session()
321 4
    for url in osurls:
322 4
        osav = networkutils.availability(url, sess)
323 4
        if osav:
324 4
            break
325
    else:
326 4
        print("OS VERSION NOT FOUND")
327 4
        cont = utilities.s2b(input("CONTINUE? Y/N: "))
328 4
        if not cont:
329 4
            print("\nEXITING...")
330 4
            raise SystemExit
331
332
333 4
def check_radio_single(radiourl, radioversion):
334
    """
335
    Check existence of single radio link.
336
337
    :param radiourl: Radio URL to check.
338
    :type radiourl: str
339
340
    :param radioversion: Radio version.
341
    :type radioversion: str
342
    """
343 4
    radav = networkutils.availability(radiourl)
344 4
    if not radav:
345 4
        print("RADIO VERSION NOT FOUND")
346 4
        cont = utilities.s2b(input("INPUT MANUALLY? Y/N: "))
347 4
        if cont:
348 4
            rad2 = input("RADIO VERSION: ")
349 4
            radiourl = radiourl.replace(radioversion, rad2)
350 4
            radioversion = rad2
351
        else:
352 4
            going = utilities.s2b(input("KEEP GOING? Y/N: "))
353 4
            if not going:
354 4
                print("\nEXITING...")
355 4
                raise SystemExit
356 4
    return radiourl, radioversion
357
358
359 4
def check_radio_bulk(radiourls, radioversion):
360
    """
361
    Check existence of list of radio links.
362
363
    :param radiourls: Radio URLs to check.
364
    :type radiourls: list(str)
365
366
    :param radioversion: Radio version.
367
    :type radioversion: str
368
    """
369 4
    sess = requests.Session()
370 4
    for url in radiourls:
371 4
        radav = networkutils.availability(url, sess)
372 4
        if radav:
373 4
            break
374
    else:
375 4
        print("RADIO VERSION NOT FOUND")
376 4
        cont = utilities.s2b(input("INPUT MANUALLY? Y/N: "))
377 4
        if cont:
378 4
            rad2 = input("RADIO VERSION: ")
379 4
            radiourls = [url.replace(radioversion, rad2) for url in radiourls]
380 4
            radioversion = rad2
381
        else:
382 4
            going = utilities.s2b(input("KEEP GOING? Y/N: "))
383 4
            if not going:
384 4
                print("\nEXITING...")
385 4
                raise SystemExit
386 4
    return radiourls, radioversion
387
388
389 4
def bulk_avail(urllist):
390
    """
391
    Filter 404 links out of URL list.
392
393
    :param urllist: URLs to check.
394
    :type urllist: list(str)
395
    """
396 4
    sess = requests.Session()
397 4
    url2 = [x for x in urllist if networkutils.availability(x, sess)]
398 4
    return url2
399
400
401 4
def get_baseurls(softwareversion, altsw=None):
402
    """
403
    Generate base URLs for bar links.
404
405
    :param softwareversion: Software version.
406
    :type softwareversion: str
407
408
    :param altsw: Radio software version, if necessary.
409
    :type altsw: str
410
    """
411 4
    baseurl = utilities.create_base_url(softwareversion)
412 4
    alturl = utilities.create_base_url(altsw) if altsw else None
413 4
    return baseurl, alturl
414
415
416 4
def get_sz_executable(compmethod):
417
    """
418
    Get 7z executable.
419
420
    :param compmethod: Compression method.
421
    :type compmethod: str
422
    """
423 4
    if compmethod != "7z":
424 4
        szexe = ""
425
    else:
426 4
        print("CHECKING PRESENCE OF 7ZIP...")
427 4
        psz = utilities.prep_seven_zip(True)
428 4
        if psz:
429 4
            print("7ZIP OK")
430 4
            szexe = utilities.get_seven_zip(False)
431
        else:
432 4
            szexe = ""
433 4
            print("7ZIP NOT FOUND")
434 4
            cont = utilities.s2b(input("CONTINUE? Y/N "))
435 4
            if cont:
436 4
                print("FALLING BACK TO ZIP...")
437 4
                compmethod = "zip"
438
            else:
439 4
                print("\nEXITING...")
440 4
                raise SystemExit  # bye bye
441 4
    return compmethod, szexe
442
443
444 4
def test_bar_files(localdir, urllist):
445
    """
446
    Test bar files after download.
447
448
    :param localdir: Directory.
449
    :type localdir: str
450
451
    :param urllist: List of URLs to check.
452
    :type urllist: list(str)
453
    """
454 4
    brokenlist = []
455 4
    print("TESTING BAR FILES...")
456 4
    for file in os.listdir(localdir):
457 4
        brokenlist = test_bar_files_individual(file, localdir, urllist, brokenlist)
458 4
    if brokenlist:
459 4
        print("SOME FILES ARE BROKEN!")
460 4
        utilities.lprint(brokenlist)
461 4
        raise SystemExit
462
    else:
463 4
        print("BAR FILES DOWNLOADED OK")
464
465
466 4
def test_bar_files_individual(file, localdir, urllist, brokenlist):
467
    """
468
    Test bar file after download.
469
470
    :param file: Bar file to check.
471
    :type file: str
472
473
    :param localdir: Directory.
474
    :type localdir: str
475
476
    :param urllist: List of URLs to check.
477
    :type urllist: list(str)
478
479
    :param brokenlist: List of URLs to download later.
480
    :type brokenlist: list(str)
481
    """
482 4
    if file.endswith(".bar"):
483 4
        print("TESTING: {0}".format(file))
484 4
        thepath = os.path.abspath(os.path.join(localdir, file))
485 4
        brokens = barutils.bar_tester(thepath)
486 4
        if brokens is not None:
487 4
            os.remove(brokens)
488 4
            for url in urllist:
489 4
                if brokens in url:
490 4
                    brokenlist.append(url)
491 4
    return brokenlist
492
493
494 4
def test_signed_files(localdir):
495
    """
496
    Test signed files after extract.
497
498
    :param localdir: Directory.
499
    :type localdir: str
500
    """
501 4
    print("TESTING SIGNED FILES...")
502 4
    for file in os.listdir(localdir):
503 4
        if file.endswith(".bar"):
504 4
            print("TESTING: {0}".format(file))
505 4
            signname, signhash = barutils.retrieve_sha512(os.path.join(localdir, file))
506 4
            sha512ver = barutils.verify_sha512(os.path.join(localdir, signname.decode("utf-8")), signhash)
507 4
            if not sha512ver:
508 4
                print("{0} IS BROKEN".format((file)))
509 4
                break
510
    else:
511 4
        print("ALL FILES EXTRACTED OK")
512
513
514 4
def test_loader_files(localdir):
515
    """
516
    Test loader files after creation.
517
518
    :param localdir: Directory.
519
    :type localdir: str
520
    """
521 4
    if not utilities.is_windows():
522 4
        pass
523
    else:
524 4
        print("TESTING LOADER FILES...")
525 4
        brokens = utilities.verify_bulk_loaders(localdir)
526 4
        if brokens:
527 4
            print("BROKEN FILES:")
528 4
            utilities.lprint(brokens)
529 4
            raise SystemExit
530
        else:
531 4
            print("ALL FILES CREATED OK")
532
533
534 4
def test_single_loader(loaderfile):
535
    """
536
    Test single loader file after creation.
537
538
    :param loaderfile: File to check.
539
    :type loaderfile: str
540
    """
541 4
    if not utilities.is_windows():
542 4
        pass
543
    else:
544 4
        print("TESTING LOADER...")
545 4
        if not utilities.verify_loader_integrity(loaderfile):
546 4
            print("{0} IS BROKEN!".format(os.path.basename(loaderfile)))
547 4
            raise SystemExit
548
        else:
549 4
            print("LOADER CREATED OK")
550
551
552 4
def prod_avail(results, mailer=False, osversion=None, password=None):
553
    """
554
    Clean availability for production lookups for autolookup script.
555
556
    :param results: Result dict.
557
    :type results: dict(str: str)
558
559
    :param mailer: If we're mailing links. Default is false.
560
    :type mailer: bool
561
562
    :param osversion: OS version.
563
    :type osversion: str
564
565
    :param password: Email password.
566
    :type password: str
567
    """
568 4
    prel = results['p']
569 4
    if prel != "SR not in system" and prel is not None:
570 4
        pav = "PD"
571 4
        baseurl = utilities.create_base_url(prel)
572 4
        avail = networkutils.availability(baseurl)
573 4
        is_avail = "Available" if avail else "Unavailable"
574 4
        prod_avail_mailprep(prel, avail, osversion, mailer, password)
575
    else:
576 4
        pav = "  "
577 4
        is_avail = "Unavailable"
578 4
    return prel, pav, is_avail
579
580
581 4
def prod_avail_mailprep(prel, avail, osversion=None, mailer=False, password=None):
582
    """
583
    Do SQL/SMTP prep work after a good production lookup hit.
584
585
    :param prel: Software lookup result.
586
    :type prel: str
587
588
    :param avail: If software lookup result is available for download.
589
    :type avail: bool
590
591
    :param osversion: OS version.
592
    :type osversion: str
593
594
    :param mailer: If we're mailing links. Default is false.
595
    :type mailer: bool
596
597
    :param password: Email password.
598
    :type password: str
599
    """
600 4
    if avail and mailer:
601 4
        sqlutils.prepare_sw_db()
602 4
        if not sqlutils.check_exists(osversion, prel):
603 4
            rad = utilities.increment(osversion, 1)
604 4
            linkgen(osversion, rad, prel, temp=True)
605 4
            smtputils.prep_email(osversion, prel, password)
606
607
608 4
def comp_joiner(rootdir, localdir, filelist):
609
    """
610
    Join rootdir, localdir to every file in filelist.
611
612
    :param rootdir: Root directory.
613
    :type rootdir: str
614
615
    :param localdir: Subfolder inside rootdir.
616
    :type localdir: str
617
618
    :param filelist: List of files to return this path for.
619
    :type filelist: list(str)
620
    """
621 4
    joinedfiles = [os.path.join(rootdir, localdir, os.path.basename(x)) for x in filelist]
622 4
    return joinedfiles
623
624
625 4
def linkgen_sdk(sdk, oses, cores):
626
    """
627
    Generate SDK debrick/core images.
628
629
    :param sdk: If we specifically want SDK images. Default is False.
630
    :type sdk: bool
631
632
    :param oses: Dictionary of radio and debrick pairs.
633
    :type oses: dict(str:str)
634
635
    :param cores: Dictionary of radio and core pairs.
636
    :type cores: dict(str:str)
637
    """
638 4
    if sdk:
639 4
        oses2 = {key: val.replace("factory_sfi", "sdk") for key, val in oses.items()}
640 4
        cores2 = {key: val.replace("factory_sfi", "sdk") for key, val in cores.items()}
641 4
        oses = {key: val.replace("verizon_sfi", "sdk") for key, val in oses2.items()}
642 4
        cores = {key: val.replace("verizon_sfi", "sdk") for key, val in cores2.items()}
643 4
    return oses, cores
644
645
646 4
def linkgen(osversion, radioversion=None, softwareversion=None, altsw=None, temp=False, sdk=False):
647
    """
648
    Generate debrick/core/radio links for given OS, radio, software release.
649
650
    :param osversion: OS version, 10.x.y.zzzz.
651
    :type osversion: str
652
653
    :param radioversion: Radio version, 10.x.y.zzzz. Can be guessed.
654
    :type radioversion: str
655
656
    :param softwareversion: Software version, 10.x.y.zzzz. Can be guessed.
657
    :type softwareversion: str
658
659
    :param altsw: Radio software release, if not the same as OS.
660
    :type altsw: str
661
662
    :param temp: If file we write to is temporary. Default is False.
663
    :type temp: bool
664
665
    :param sdk: If we specifically want SDK images. Default is False.
666
    :type sdk: bool
667
    """
668 4
    radioversion = return_radio_version(osversion, radioversion)
669 4
    softwareversion, swc = return_sw_checked(softwareversion, osversion)
670 4
    del swc
671 4
    if altsw is not None:
672 4
        altsw, aswc = return_radio_sw_checked(altsw, radioversion)
673 4
        del aswc
674 4
    baseurl = utilities.create_base_url(softwareversion)
675 4
    oses, cores, radios = textgenerator.url_gen(osversion, radioversion, softwareversion)
676 4
    if altsw is not None:
677 4
        del radios
678 4
        dbks, cors, radios = textgenerator.url_gen(osversion, radioversion, altsw)
679 4
        del dbks
680 4
        del cors
681 4
    avlty = networkutils.availability(baseurl)
682 4
    oses, cores = linkgen_sdk(sdk, oses, cores)
683 4
    prargs = (softwareversion, osversion, radioversion, oses, cores, radios, avlty, False, None, temp, altsw)
684 4
    lthr = threading.Thread(target=textgenerator.write_links, args=prargs)
685 4
    lthr.start()
686
687
688 4
def clean_swrel(swrelset):
689
    """
690
    Clean a list of software release lookups.
691
692
    :param swrelset: List of software releases.
693
    :type swrelset: set(str)
694
    """
695 4
    for i in swrelset:
696 4
        if i != "SR not in system" and i is not None:
697 4
            swrelease = i
698 4
            break
699
    else:
700 4
        swrelease = ""
701 4
    return swrelease
702
703
704 4
def autolookup_logger(record, out):
705
    """
706
    Write autolookup results to file.
707
708
    :param record: The file to log to.
709
    :type record: str
710
711
    :param out: Output block.
712
    :type out: str
713
    """
714 4
    with open(record, "a") as rec:
715 4
        rec.write("{0}\n".format(out))
716
717
718 4
def autolookup_printer(out, avail, log=False, quiet=False, record=None):
719
    """
720
    Print autolookup results, logging if specified.
721
722
    :param out: Output block.
723
    :type out: str
724
725
    :param avail: Availability. Can be "Available" or "Unavailable".
726
    :type avail: str
727
728
    :param log: If we're logging to file.
729
    :type log: bool
730
731
    :param quiet: If we only note available entries.
732
    :type quiet: bool
733
734
    :param record: If we're logging, the file to log to.
735
    :type record: str
736
    """
737 4
    if not quiet:
738 4
        avail = "Available"  # force things
739 4
    if avail.lower() == "available":
740 4
        if log:
741 4
            lthr = threading.Thread(target=autolookup_logger, args=(record, out))
742 4
            lthr.start()
743 4
        print(out)
744
745
746 4
def autolookup_output_sql(osversion, swrelease, avail, sql=False):
747
    """
748
    Add OS to SQL database.
749
750
    :param osversion: OS version.
751
    :type osversion: str
752
753
    :param swrelease: Software release.
754
    :type swrelease: str
755
756
    :param avail: "Unavailable" or "Available".
757
    :type avail: str
758
759
    :param sql: If we're adding this to our SQL database.
760
    :type sql: bool
761
    """
762 4
    if sql:
763 4
        sqlutils.prepare_sw_db()
764 4
        if not sqlutils.check_exists(osversion, swrelease):
765 4
            sqlutils.insert(osversion, swrelease, avail.lower())
766
767
768 4
def autolookup_output(osversion, swrelease, avail, avpack, sql=False):
769
    """
770
    Prepare autolookup block, and add to SQL database.
771
772
    :param osversion: OS version.
773
    :type osversion: str
774
775
    :param swrelease: Software release.
776
    :type swrelease: str
777
778
    :param avail: "Unavailable" or "Available".
779
    :type avail: str
780
781
    :param avpack: Availabilities: alpha 1 and 2, beta 1 and 2, production.
782
    :type avpack: list(str)
783
784
    :param sql: If we're adding this to our SQL database.
785
    :type sql: bool
786
    """
787 4
    othr = threading.Thread(target=autolookup_output_sql, args=(osversion, swrelease, avail, sql))
788 4
    othr.start()
789 4
    avblok = "[{0}|{1}|{2}|{3}|{4}]".format(*avpack)
790 4
    out = "OS {0} - SR {1} - {2} - {3}".format(osversion, swrelease, avblok, avail)
791 4
    return out
792
793
794 4
def clean_barlist(cleanfiles, stoppers):
795
    """
796
    Remove certain bars from barlist based on keywords.
797
798
    :param cleanfiles: List of files to clean.
799
    :type cleanfiles: list(str)
800
801
    :param stoppers: List of keywords (i.e. bar names) to exclude.
802
    :type stoppers: list(str)
803
    """
804 4
    finals = [link for link in cleanfiles if all(word not in link for word in stoppers)]
805 4
    return finals
806
807
808 4
def prep_export_cchecker(files, npc, hwid, osv, radv, swv, upgrade=False, forced=None):
809
    """
810
    Prepare carrierchecker lookup links to write to file.
811
812
    :param files: List of file URLs.
813
    :type files: list(str)
814
815
    :param npc: MCC + MNC (ex. 302220).
816
    :type npc: int
817
818
    :param hwid: Device hardware ID.
819
    :type hwid: str
820
821
    :param osv: OS version.
822
    :type osv: str
823
824
    :param radv: Radio version.
825
    :type radv: str
826
827
    :param swv: Software release.
828
    :type swv: str
829
830
    :param upgrade: Whether or not to use upgrade files. Default is false.
831
    :type upgrade: bool
832
833
    :param forced: Force a software release. None to go for latest.
834
    :type forced: str
835
    """
836 4
    if not upgrade:
837 4
        newfiles = networkutils.carrier_query(npc, hwid, True, False, forced)
838 4
        cleanfiles = newfiles[3]
839
    else:
840 4
        cleanfiles = files
841 4
    osurls, coreurls, radiourls = textgenerator.url_gen(osv, radv, swv)
842 4
    stoppers = ["8960", "8930", "8974", "m5730", "winchester"]
843 4
    finals = clean_barlist(cleanfiles, stoppers)
844 4
    return osurls, coreurls, radiourls, finals
845
846
847 4
def export_cchecker(files, npc, hwid, osv, radv, swv, upgrade=False, forced=None):
848
    """
849
    Write carrierchecker lookup links to file.
850
851
    :param files: List of file URLs.
852
    :type files: list(str)
853
854
    :param npc: MCC + MNC (ex. 302220).
855
    :type npc: int
856
857
    :param hwid: Device hardware ID.
858
    :type hwid: str
859
860
    :param osv: OS version.
861
    :type osv: str
862
863
    :param radv: Radio version.
864
    :type radv: str
865
866
    :param swv: Software release.
867
    :type swv: str
868
869
    :param upgrade: Whether or not to use upgrade files. Default is false.
870
    :type upgrade: bool
871
872
    :param forced: Force a software release. None to go for latest.
873
    :type forced: str
874
    """
875 4
    if files:
876 4
        osurls, coreurls, radiourls, finals = prep_export_cchecker(files, npc, hwid, osv, radv, swv, upgrade, forced)
877 4
        textgenerator.write_links(swv, osv, radv, osurls, coreurls, radiourls, True, True, finals)
878 4
        print("\nFINISHED!!!")
879
    else:
880 4
        print("CANNOT EXPORT, NO SOFTWARE RELEASE")
881
882
883 4
def generate_blitz_links(files, osv, radv, swv):
884
    """
885
    Generate blitz URLs (i.e. all OS and radio links).
886
    :param files: List of file URLs.
887
    :type files: list(str)
888
889
    :param osv: OS version.
890
    :type osv: str
891
892
    :param radv: Radio version.
893
    :type radv: str
894
895
    :param swv: Software release.
896
    :type swv: str
897
    """
898 4
    coreurls = [
899
        utilities.create_bar_url(swv, "winchester.factory_sfi", osv),
900
        utilities.create_bar_url(swv, "qc8960.factory_sfi", osv),
901
        utilities.create_bar_url(swv, "qc8960.factory_sfi", osv),
902
        utilities.create_bar_url(swv, "qc8960.factory_sfi_hybrid_qc8974", osv)
903
    ]
904 4
    radiourls = [
905
        utilities.create_bar_url(swv, "m5730", radv),
906
        utilities.create_bar_url(swv, "qc8960", radv),
907
        utilities.create_bar_url(swv, "qc8960.wtr", radv),
908
        utilities.create_bar_url(swv, "qc8960.wtr5", radv),
909
        utilities.create_bar_url(swv, "qc8930.wtr5", radv),
910
        utilities.create_bar_url(swv, "qc8974.wtr2", radv)
911
    ]
912 4
    return files + coreurls + radiourls
913
914
915 4
def package_blitz(bardir, swv):
916
    """
917
    Package and verify a blitz package.
918
919
    :param bardir: Path to folder containing bar files.
920
    :type bardir: str
921
922
    :param swv: Software version.
923
    :type swv: str
924
    """
925 4
    print("\nCREATING BLITZ...")
926 4
    barutils.create_blitz(bardir, swv)
927 4
    print("\nTESTING BLITZ...")
928 4
    zipver = archiveutils.zip_verify("Blitz-{0}.zip".format(swv))
929 4
    if not zipver:
930 4
        print("BLITZ FILE IS BROKEN")
931 4
        raise SystemExit
932
    else:
933 4
        shutil.rmtree(bardir)
934
935
936 4
def slim_preamble(appname):
937
    """
938
    Standard app name header.
939
940
    :param appname: Name of app.
941
    :type appname: str
942
    """
943 4
    print("~~~{0} VERSION {1}~~~".format(appname.upper(), shortversion()))
944
945
946 4
def standard_preamble(appname, osversion, softwareversion, radioversion, altsw=None):
947
    """
948
    Standard app name, OS, radio and software (plus optional radio software) print block.
949
950
    :param appname: Name of app.
951
    :type appname: str
952
953
    :param osversion: OS version, 10.x.y.zzzz. Required.
954
    :type osversion: str
955
956
    :param radioversion: Radio version, 10.x.y.zzzz. Can be guessed.
957
    :type radioversion: str
958
959
    :param softwareversion: Software release, 10.x.y.zzzz. Can be guessed.
960
    :type softwareversion: str
961
962
    :param altsw: Radio software release, if not the same as OS.
963
    :type altsw: str
964
    """
965 4
    slim_preamble(appname)
966 4
    print("OS VERSION: {0}".format(osversion))
967 4
    print("OS SOFTWARE VERSION: {0}".format(softwareversion))
968 4
    print("RADIO VERSION: {0}".format(radioversion))
969 4
    if altsw is not None:
970 4
        print("RADIO SOFTWARE VERSION: {0}".format(altsw))
971
972
973 4
def questionnaire_device(message=None):
974
    """
975
    Get device from questionnaire.
976
    """
977 4
    message = "DEVICE (SXX100-#): " if message is None else message
978 4
    device = input(message)
979 4
    if not device:
980 4
        print("NO DEVICE SPECIFIED!")
981 4
        decorators.enter_to_exit(True)
982 4
        if not getattr(sys, 'frozen', False):
983 4
            raise SystemExit
984 4
    return device
985
986
987 4
def verify_gpg_credentials():
988
    """
989
    Read GPG key/pass from file, verify if incomplete.
990
    """
991 4
    gpgkey, gpgpass = gpgutils.gpg_config_loader()
992 4
    if gpgkey is None or gpgpass is None:
993 4
        print("NO PGP KEY/PASS FOUND")
994 4
        cont = utilities.s2b(input("CONTINUE (Y/N)?: "))
995 4
        if cont:
996 4
            gpgkey = verify_gpg_key(gpgkey)
997 4
            gpgpass, writebool = verify_gpg_pass(gpgpass)
998 4
            gpgpass2 = gpgpass if writebool else None
999 4
            gpgutils.gpg_config_writer(gpgkey, gpgpass2)
1000
        else:
1001 4
            gpgkey = None
1002 4
    return gpgkey, gpgpass
1003
1004
1005 4
def verify_gpg_key(gpgkey=None):
1006
    """
1007
    Verify GPG key.
1008
1009
    :param gpgkey: Key, use None to take from input.
1010
    :type gpgkey: str
1011
    """
1012 4
    if gpgkey is None:
1013 4
        gpgkey = input("PGP KEY (0x12345678): ")
1014 4
        if not gpgkey.startswith("0x"):
1015 4
            gpgkey = "0x{0}".format(gpgkey)   # add preceding 0x
1016 4
    return gpgkey
1017
1018
1019 4
def verify_gpg_pass(gpgpass=None):
1020
    """
1021
    Verify GPG passphrase.
1022
1023
    :param gpgpass: Passphrase, use None to take from input.
1024
    :type gpgpass: str
1025
    """
1026 4
    if gpgpass is None:
1027 4
        gpgpass = getpass.getpass(prompt="PGP PASSPHRASE: ")
1028 4
        writebool = utilities.s2b(input("SAVE PASSPHRASE (Y/N)?:"))
1029
    else:
1030 4
        writebool = False
1031 4
    return gpgpass, writebool
1032
1033
1034 4
def bulk_hash(dirs, compressed=True, deleted=True, radios=True, hashdict=None):
1035
    """
1036
    Hash files in several folders based on flags.
1037
1038
    :param dirs: Folders: [OS_bars, radio_bars, OS_exes, radio_exes, OS_zips, radio_zips]
1039
    :type dirs: list(str)
1040
1041
    :param compressed: Whether to hash compressed files. True by default.
1042
    :type compressed: bool
1043
1044
    :param deleted: Whether to delete uncompressed files. True by default.
1045
    :type deleted: bool
1046
1047
    :param radios: Whether to hash radio autoloaders. True by default.
1048
    :type radios: bool
1049
1050
    :param hashdict: Dictionary of hash rules, in ~\bbarchivist.ini.
1051
    :type hashdict: dict({str: bool})
1052
    """
1053 4
    print("HASHING LOADERS...")
1054 4
    utilities.cond_check(hashutils.verifier, utilities.def_args(dirs), [hashdict], radios, compressed, deleted)
1055
1056
1057 4
def bulk_verify(dirs, compressed=True, deleted=True, radios=True):
1058
    """
1059
    Verify files in several folders based on flags.
1060
1061
    :param dirs: Folders: [OS_bars, radio_bars, OS_exes, radio_exes, OS_zips, radio_zips]
1062
    :type dirs: list(str)
1063
1064
    :param compressed: Whether to hash compressed files. True by default.
1065
    :type compressed: bool
1066
1067
    :param deleted: Whether to delete uncompressed files. True by default.
1068
    :type deleted: bool
1069
1070
    :param radios: Whether to hash radio autoloaders. True by default.
1071
    :type radios: bool
1072
    """
1073 4
    gpgkey, gpgpass = verify_gpg_credentials()
1074 4
    if gpgpass is not None and gpgkey is not None:
1075 4
        print("VERIFYING LOADERS...")
1076 4
        print("KEY: {0}".format(gpgkey))
1077 4
        restargs = [gpgkey, gpgpass, True]
1078 4
        utilities.cond_check(gpgutils.gpgrunner, utilities.def_args(dirs), restargs, radios, compressed, deleted)
1079
1080
1081 4
def enn_ayy(quant):
1082
    """
1083
    Cheeky way to put a N/A placeholder for a string.
1084
1085
    :param quant: What to check if it's None.
1086
    :type quant: str
1087
    """
1088 4
    return "N/A" if quant is None else quant
1089
1090
1091 4
def generate_workfolder(folder=None):
1092
    """
1093
    Check if a folder exists, make it if it doesn't, set it to home if None.
1094
1095
    :param folder: Folder to check.
1096
    :type folder: str
1097
    """
1098 4
    if folder is None:
1099 4
        folder = os.getcwd()
1100 4
    elif folder is not None and not os.path.exists(folder):
1101 4
        os.makedirs(folder)
1102 4
    return folder
1103
1104
1105 4
def info_header(afile, osver, radio=None, software=None, device=None):
1106
    """
1107
    Write header for info file.
1108
1109
    :param afile: Open file to write to.
1110
    :type afile: File object
1111
1112
    :param osver: OS version, required for both types.
1113
    :type osver: str
1114
1115
    :param radio: Radio version, required for QNX.
1116
    :type radio: str
1117
1118
    :param software: Software release, required for QNX.
1119
    :type software: str
1120
1121
    :param device: Device type, required for Android.
1122
    :type device: str
1123
    """
1124 4
    afile.write("OS: {0}\n".format(osver))
1125 4
    if device:
1126 4
        afile.write("Device: {0}\n".format(enn_ayy(device)))
1127
    else:
1128 4
        afile.write("Radio: {0}\n".format(enn_ayy(radio)))
1129 4
        afile.write("Software: {0}\n".format(enn_ayy(software)))
1130 4
    afile.write("{0}\n".format("~"*40))
1131
1132
1133 4
def prep_info(filepath, osver, device=None):
1134
    """
1135
    Prepare file list for new-style info file.
1136
1137
    :param filepath: Path to folder to analyze.
1138
    :type filepath: str
1139
1140
    :param osver: OS version, required for both types.
1141
    :type osver: str
1142
1143
    :param device: Device type, required for Android.
1144
    :type device: str
1145
    """
1146 4
    fileext = ".zip" if device else ".7z"
1147 4
    files = os.listdir(filepath)
1148 4
    absfiles = [os.path.join(filepath, x) for x in files if x.endswith((fileext, ".exe"))]
1149 4
    fname = os.path.join(filepath, "!{0}_OSINFO!.txt".format(osver))
1150 4
    return fname, absfiles
1151
1152
1153 4
def make_info(filepath, osver, radio=None, software=None, device=None):
1154
    """
1155
    Create a new-style info (names, sizes and hashes) file.
1156
1157
    :param filepath: Path to folder to analyze.
1158
    :type filepath: str
1159
1160
    :param osver: OS version, required for both types.
1161
    :type osver: str
1162
1163
    :param radio: Radio version, required for QNX.
1164
    :type radio: str
1165
1166
    :param software: Software release, required for QNX.
1167
    :type software: str
1168
1169
    :param device: Device type, required for Android.
1170
    :type device: str
1171
    """
1172 4
    fname, absfiles = prep_info(filepath, osver, device)
1173 4
    with open(fname, "w") as afile:
1174 4
        info_header(afile, osver, radio, software, device)
1175 4
        for indx, file in enumerate(absfiles):
1176 4
            write_info(file, indx, len(absfiles), afile)
1177
1178
1179 4
def write_info(infile, index, filecount, outfile):
1180
    """
1181
    Write a new-style info (names, sizes and hashes) file.
1182
1183
    :param infile: Path to file whose name, size and hash are to be written.
1184
    :type infile: str
1185
1186
    :param index: Which file index out of the list of files we're writing.
1187
    :type index: int
1188
1189
    :param filecount: Total number of files we're to write; for excluding terminal newline.
1190
    :type filecount: int
1191
1192
    :param outfile: Open (!!!) file handle. Output file.
1193
    :type outfile: str
1194
    """
1195 4
    fsize = os.stat(infile).st_size
1196 4
    outfile.write("File: {0}\n".format(os.path.basename(infile)))
1197 4
    outfile.write("\tSize: {0} ({1})\n".format(fsize, utilities.fsizer(fsize)))
1198 4
    outfile.write("\tHashes:\n")
1199 4
    outfile.write("\t\tMD5: {0}\n".format(hashutils.hashlib_hash(infile, hashlib.md5()).upper()))
1200 4
    outfile.write("\t\tSHA1: {0}\n".format(hashutils.hashlib_hash(infile, hashlib.sha1()).upper()))
1201 4
    outfile.write("\t\tSHA256: {0}\n".format(hashutils.hashlib_hash(infile, hashlib.sha256()).upper()))
1202 4
    outfile.write("\t\tSHA512: {0}\n".format(hashutils.hashlib_hash(infile, hashlib.sha512()).upper()))
1203 4
    if index != filecount - 1:
1204 4
        outfile.write("\n")
1205
1206
1207 4
def bulk_info(dirs, osv, compressed=True, deleted=True, radios=True, rad=None, swv=None, dev=None):
1208
    """
1209
    Generate info files in several folders based on flags.
1210
1211
    :param dirs: Folders: [OS_bars, radio_bars, OS_exes, radio_exes, OS_zips, radio_zips]
1212
    :type dirs: list(str)
1213
1214
    :param osver: OS version, required for both types.
1215
    :type osver: str
1216
1217
    :param compressed: Whether to hash compressed files. True by default.
1218
    :type compressed: bool
1219
1220
    :param deleted: Whether to delete uncompressed files. True by default.
1221
    :type deleted: bool
1222
1223
    :param radios: Whether to hash radio autoloaders. True by default.
1224
    :type radios: bool
1225
1226
    :param rad: Radio version, required for QNX.
1227
    :type rad: str
1228
1229
    :param swv: Software release, required for QNX.
1230
    :type swv: str
1231
1232
    :param dev: Device type, required for Android.
1233
    :type dev: str
1234
    """
1235 4
    print("GENERATING INFO FILES...")
1236 4
    restargs = [osv, rad, swv, dev]
1237
    utilities.cond_check(make_info, utilities.def_args(dirs), restargs, radios, compressed, deleted)
1238