Completed
Push — master ( 85b707...d42730 )
by John
03:18
created

tcl_delta_filename()   A

Complexity

Conditions 2

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

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