Completed
Push — master ( dfeea4...b9163a )
by John
03:32
created

prep_logfile_folder()   A

Complexity

Conditions 2

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
dl 0
loc 10
ccs 6
cts 6
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
#!/usr/bin/env python3
2 5
"""This module is used for miscellaneous utilities."""
3
4 5
import os  # path work
5 5
import argparse  # argument parser for filters
6 5
import platform  # platform info
7 5
import glob  # cap grabbing
8 5
import hashlib  # base url creation
9 5
import threading  # get thread for spinner
10 5
import time  # spinner delay
11 5
import sys  # streams, version info
12 5
import itertools  # spinners gonna spin
13 5
import subprocess  # loader verification
14 5
from bbarchivist import bbconstants  # cap location, version, filename bits
15 5
from bbarchivist import compat  # backwards compat
16 5
from bbarchivist import dummy  # useless stdout
17 5
from bbarchivist import exceptions  # exceptions
18 5
from bbarchivist import iniconfig  # config parsing
19
20 5
__author__ = "Thurask"
21 5
__license__ = "WTFPL v2"
22 5
__copyright__ = "2015-2018 Thurask"
23
24
25 5
def grab_datafile(datafile):
26
    """
27
    Figure out where a datafile is.
28
29
    :param datafile: Datafile to check.
30
    :type datafile: bbconstants.Datafile
31
    """
32 5
    try:
33 5
        afile = glob.glob(os.path.join(os.getcwd(), datafile.filename))[0]
34 5
    except IndexError:
35 5
        afile = datafile.location if datafile.name == "cfp" else grab_capini(datafile)
36 5
    return os.path.abspath(afile)
37
38
39 5
def grab_capini(datafile):
40
    """
41
    Get cap location from .ini file, and write if it's new.
42
43
    :param datafile: Datafile to check.
44
    :type datafile: bbconstants.Datafile
45
    """
46 5
    try:
47 5
        apath = cappath_config_loader()
48 5
        afile = glob.glob(apath)[0]
49 5
    except IndexError:
50 5
        cappath_config_writer(datafile.location)
51 5
        return bbconstants.CAP.location  # no ini cap
52
    else:
53 5
        cappath_config_writer(os.path.abspath(afile))
54 5
        return os.path.abspath(afile)  # ini cap
55
56
57 5
def grab_cap():
58
    """
59
    Figure out where cap is, local, specified or system-supplied.
60
    """
61 5
    return grab_datafile(bbconstants.CAP)
62
63
64 5
def grab_cfp():
65
    """
66
    Figure out where cfp is, local or system-supplied.
67
    """
68 5
    return grab_datafile(bbconstants.CFP)
69
70
71 5
def new_enough(minver):
72
    """
73
    Check if we're at or above a minimum Python version.
74
75
    :param minver: Minimum Python version (3.minver).
76
    :type minver: int
77
    """
78 5
    return False if minver > sys.version_info[1] else True
79
80
81 5
def dirhandler(directory, defaultdir):
82
    """
83
    If directory is None, turn it into defaultdir.
84
85
    :param directory: Target directory.
86
    :type directory: str
87
88
    :param defaultdir: Default directory.
89
    :type defaultdir: str
90
    """
91 5
    directory = defaultdir if directory is None else directory
92 5
    return directory
93
94
95 5
def fsizer(file_size):
96
    """
97
    Raw byte file size to human-readable string.
98
99
    :param file_size: Number to parse.
100
    :type file_size: float
101
    """
102 5
    fsize = prep_filesize(file_size)
103 5
    for sfix in ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']:
104 5
        if fsize < 1024.0:
105 5
            size = "{0:3.2f}{1}".format(fsize, sfix)
106 5
            break
107
        else:
108 5
            fsize /= 1024.0
109
    else:
110 5
        size = "{0:3.2f}{1}".format(fsize, 'YB')
111 5
    return size
112
113
114 5
def prep_filesize(file_size):
115
    """
116
    Convert file size to float.
117
118
    :param file_size: Number to parse.
119
    :type file_size: float
120
    """
121 5
    if file_size is None:
122 5
        file_size = 0.0
123 5
    fsize = float(file_size)
124 5
    return fsize
125
126
127 5
def signed_file_args(files):
128
    """
129
    Check if there are between 1 and 6 files supplied to argparse.
130
131
    :param files: List of signed files, between 1 and 6 strings.
132
    :type files: list(str)
133
    """
134 5
    filelist = [file for file in files if file]
135 5
    if not 1 <= len(filelist) <= 6:
136 5
        raise argparse.ArgumentError(argument=None, message="Requires 1-6 signed files")
137 5
    return files
138
139
140 5
def file_exists(file):
141
    """
142
    Check if file exists, raise argparse error if it doesn't.
143
144
    :param file: Path to a file, including extension.
145
    :type file: str
146
    """
147 5
    if not os.path.exists(file):
148 5
        raise argparse.ArgumentError(argument=None, message="{0} not found.".format(file))
149 5
    return file
150
151
152 5
def positive_integer(input_int):
153
    """
154
    Check if number > 0, raise argparse error if it isn't.
155
156
    :param input_int: Integer to check.
157
    :type input_int: str
158
    """
159 5
    if int(input_int) <= 0:
160 5
        info = "{0} is not >=0.".format(str(input_int))
161 5
        raise argparse.ArgumentError(argument=None, message=info)
162 5
    return int(input_int)
163
164
165 5
def valid_method_poptxz(methodlist):
166
    """
167
    Remove .tar.xz support if system is too old.
168
169
    :param methodlist: List of all methods.
170
    :type methodlist: tuple(str)
171
    """
172 5
    if not new_enough(3):
173 5
        methodlist = [x for x in bbconstants.METHODS if x != "txz"]
174 5
    return methodlist
175
176
177 5
def valid_method(method):
178
    """
179
    Check if compression method is valid, raise argparse error if it isn't.
180
181
    :param method: Compression method to check.
182
    :type method: str
183
    """
184 5
    methodlist = bbconstants.METHODS
185 5
    methodlist = valid_method_poptxz(methodlist)
186 5
    if method not in methodlist:
187 5
        info = "Invalid method {0}.".format(method)
188 5
        raise argparse.ArgumentError(argument=None, message=info)
189 5
    return method
190
191
192 5
def valid_carrier(mcc_mnc):
193
    """
194
    Check if MCC/MNC is valid (1-3 chars), raise argparse error if it isn't.
195
196
    :param mcc_mnc: MCC/MNC to check.
197
    :type mcc_mnc: str
198
    """
199 5
    if not str(mcc_mnc).isdecimal():
200 5
        infod = "Non-integer {0}.".format(str(mcc_mnc))
201 5
        raise argparse.ArgumentError(argument=None, message=infod)
202 5
    if len(str(mcc_mnc)) > 3 or len(str(mcc_mnc)) == 0:
203 5
        infol = "{0} is invalid.".format(str(mcc_mnc))
204 5
        raise argparse.ArgumentError(argument=None, message=infol)
205
    else:
206 5
        return mcc_mnc
207
208
209 5
def escreens_pin(pin):
210
    """
211
    Check if given PIN is valid, raise argparse error if it isn't.
212
213
    :param pin: PIN to check.
214
    :type pin: str
215
    """
216 5
    if len(pin) == 8:
217 5
        try:
218 5
            int(pin, 16)  # hexadecimal-ness
219 5
        except ValueError:
220 5
            raise argparse.ArgumentError(argument=None, message="Invalid PIN.")
221
        else:
222 5
            return pin.lower()
223
    else:
224 5
        raise argparse.ArgumentError(argument=None, message="Invalid PIN.")
225
226
227 5
def escreens_duration(duration):
228
    """
229
    Check if Engineering Screens duration is valid.
230
231
    :param duration: Duration to check.
232
    :type duration: int
233
    """
234 5
    if int(duration) in (1, 3, 6, 15, 30):
235 5
        return int(duration)
236
    else:
237 5
        raise argparse.ArgumentError(argument=None, message="Invalid duration.")
238
239
240 5
def droidlookup_hashtype(method):
241
    """
242
    Check if Android autoloader lookup hash type is valid.
243
244
    :param method: None for regular OS links, "sha256/512" for SHA256 or 512 hash.
245
    :type method: str
246
    """
247 5
    if method.lower() in ("sha512", "sha256"):
248 5
        return method.lower()
249
    else:
250 5
        raise argparse.ArgumentError(argument=None, message="Invalid type.")
251
252
253 5
def droidlookup_devicetype(device):
254
    """
255
    Check if Android autoloader device type is valid.
256
257
    :param device: Android autoloader types to check.
258
    :type device: str
259
    """
260 5
    devices = ("Priv", "DTEK50", "DTEK60", "KEYone", "Aurora", "Motion")
261 5
    if device is None:
262 5
        return None
263
    else:
264 5
        for dev in devices:
265 5
            if dev.lower() == device.lower():
266 5
                return dev
267 5
        raise argparse.ArgumentError(argument=None, message="Invalid device.")
268
269
270 5
def s2b(input_check):
271
    """
272
    Return Boolean interpretation of string input.
273
274
    :param input_check: String to check if it means True or False.
275
    :type input_check: str
276
    """
277 5
    return str(input_check).lower() in ("yes", "true", "t", "1", "y")
278
279
280 5
def i2b(input_check):
281
    """
282
    Return Boolean interpretation of typed input.
283
284
    :param input_check: Query to feed into input function.
285
    :type input_check: str
286
    """
287 5
    return s2b(input(input_check))
288
289
290 5
def is_amd64():
291
    """
292
    Check if script is running on an AMD64 system (Python can be 32/64, this is for subprocess)
293
    """
294 5
    return platform.machine().endswith("64")
295
296
297 5
def is_windows():
298
    """
299
    Check if script is running on Windows.
300
    """
301 5
    return platform.system() == "Windows"
302
303
304 5
def talkaprint(msg, talkative=False):
305
    """
306
    Print only if asked to.
307
308
    :param msg: Message to print.
309
    :type msg: str
310
311
    :param talkative: Whether to output to screen. False by default.
312
    :type talkative: bool
313
    """
314 5
    if talkative:
315 5
        print(msg)
316
317
318 5
def get_seven_zip(talkative=False):
319
    """
320
    Return name of 7-Zip executable.
321
    On POSIX, it MUST be 7za.
322
    On Windows, it can be installed or supplied with the script.
323
    :func:`win_seven_zip` is used to determine if it's installed.
324
325
    :param talkative: Whether to output to screen. False by default.
326
    :type talkative: bool
327
    """
328 5
    return win_seven_zip(talkative) if is_windows() else "7za"
329
330
331 5
def win_seven_zip(talkative=False):
332
    """
333
    For Windows, check where 7-Zip is ("where", pretty much).
334
    Consult registry first for any installed instances of 7-Zip.
335
336
    :param talkative: Whether to output to screen. False by default.
337
    :type talkative: bool
338
    """
339 5
    talkaprint("CHECKING INSTALLED FILES...", talkative)
340 5
    try:
341 5
        path = wsz_registry()
342 5
    except OSError as exc:
343 5
        if talkative:
344 5
            exceptions.handle_exception(exc, xit=None)
345 5
        talkaprint("TRYING LOCAL FILES...", talkative)
346 5
        return win_seven_zip_local(talkative)
347
    else:
348
        talkaprint("7ZIP USING INSTALLED FILES", talkative)
349
        return '"{0}"'.format(os.path.join(path[0], "7z.exe"))
350
351
352 5
def wsz_registry():
353
    """
354
    Check Windows registry for 7-Zip executable location.
355
    """
356
    import winreg  # windows registry
357
    hk7z = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\7-Zip")
358
    path = winreg.QueryValueEx(hk7z, "Path")
359
    return path
360
361
362 5
def win_seven_zip_local(talkative=False):
363
    """
364
    If 7-Zip isn't in the registry, fall back onto supplied executables.
365
    If *those* aren't there, return "error".
366
367
    :param talkative: Whether to output to screen. False by default.
368
    :type talkative: bool
369
    """
370 5
    filecount = wsz_filecount()
371 5
    if filecount == 2:
372 5
        szexe = wsz_local_good(talkative)
373
    else:
374 5
        szexe = wsz_local_bad(talkative)
375 5
    return szexe
376
377
378 5
def wsz_filecount():
379
    """
380
    Get count of 7-Zip executables in local folder.
381
    """
382 5
    filecount = len([x for x in os.listdir(os.getcwd()) if x in ["7za.exe", "7za64.exe"]])
383 5
    return filecount
384
385
386 5
def wsz_local_good(talkative=False):
387
    """
388
    Get 7-Zip exe name if everything is good.
389
390
    :param talkative: Whether to output to screen. False by default.
391
    :type talkative: bool
392
    """
393 5
    talkaprint("7ZIP USING LOCAL FILES", talkative)
394 5
    szexe = "7za64.exe" if is_amd64() else "7za.exe"
395 5
    return szexe
396
397
398 5
def wsz_local_bad(talkative=False):
399
    """
400
    Handle 7-Zip exe name in case of issues.
401
402
    :param talkative: Whether to output to screen. False by default.
403
    :type talkative: bool
404
    """
405 5
    talkaprint("NO LOCAL FILES", talkative)
406 5
    szexe = "error"
407 5
    return szexe
408
409
410 5
def get_core_count():
411
    """
412
    Find out how many CPU cores this system has.
413
    """
414 5
    try:
415 5
        cores = str(compat.enum_cpus())  # 3.4 and up
416 5
    except NotImplementedError:
417 5
        cores = "1"  # 3.2-3.3
418
    else:
419 5
        if compat.enum_cpus() is None:
420 5
            cores = "1"
421 5
    return cores
422
423
424 5
def prep_seven_zip_path(path, talkative=False):
425
    """
426
    Print p7zip path on POSIX, or notify if not there.
427
428
    :param path: Path to use.
429
    :type path: str
430
431
    :param talkative: Whether to output to screen. False by default.
432
    :type talkative: bool
433
    """
434 5
    if path is None:
435 5
        talkaprint("NO 7ZIP\nPLEASE INSTALL p7zip", talkative)
436 5
        return False
437
    else:
438 5
        talkaprint("7ZIP FOUND AT {0}".format(path), talkative)
439 5
        return True
440
441
442 5
def prep_seven_zip_posix(talkative=False):
443
    """
444
    Check for p7zip on POSIX.
445
446
    :param talkative: Whether to output to screen. False by default.
447
    :type talkative: bool
448
    """
449 5
    try:
450 5
        path = compat.where_which("7za")
451 5
    except ImportError:
452 5
        talkaprint("PLEASE INSTALL SHUTILWHICH WITH PIP", talkative)
453 5
        return False
454
    else:
455 5
        return prep_seven_zip_path(path, talkative)
456
457
458 5
def prep_seven_zip(talkative=False):
459
    """
460
    Check for presence of 7-Zip.
461
    On POSIX, check for p7zip.
462
    On Windows, check for 7-Zip.
463
464
    :param talkative: Whether to output to screen. False by default.
465
    :type talkative: bool
466
    """
467 5
    if is_windows():
468
        return get_seven_zip(talkative) != "error"
469
    else:
470 5
        return prep_seven_zip_posix(talkative)
471
472
473 5
def increment(version, inc=3):
474
    """
475
    Increment version by given number. For repeated lookups.
476
477
    :param version: w.x.y.ZZZZ, becomes w.x.y.(ZZZZ + increment).
478
    :type version: str
479
480
    :param inc: What to increment by. Default is 3.
481
    :type inc: str
482
    """
483 5
    splitos = version.split(".")
484 5
    splitos[3] = int(splitos[3])
485 5
    if splitos[3] > 9996:  # prevent overflow
486 5
        splitos[3] = 0
487 5
    splitos[3] += int(inc)
488 5
    splitos[3] = str(splitos[3])
489 5
    return ".".join(splitos)
490
491
492 5
def stripper(name):
493
    """
494
    Strip fluff from bar filename.
495
496
    :param name: Bar filename, must contain '-nto+armle-v7+signed.bar'.
497
    :type name: str
498
    """
499 5
    return name.replace("-nto+armle-v7+signed.bar", "")
500
501
502 5
def create_base_url(softwareversion):
503
    """
504
    Make the root URL for production server files.
505
506
    :param softwareversion: Software version to hash.
507
    :type softwareversion: str
508
    """
509
    # Hash software version
510 5
    swhash = hashlib.sha1(softwareversion.encode('utf-8'))
511 5
    hashedsoftwareversion = swhash.hexdigest()
512
    # Root of all urls
513 5
    baseurl = "http://cdn.fs.sl.blackberry.com/fs/qnx/production/{0}".format(hashedsoftwareversion)
514 5
    return baseurl
515
516
517 5
def format_app_name(appname):
518
    """
519
    Convert long reverse DNS name to short name.
520
521
    :param appname: Application name (ex. sys.pim.calendar -> "calendar")
522
    :type appname: str
523
    """
524 5
    final = appname.split(".")[-1]
525 5
    return final
526
527
528 5
def create_bar_url(softwareversion, appname, appversion, clean=False):
529
    """
530
    Make the URL for any production server file.
531
532
    :param softwareversion: Software version to hash.
533
    :type softwareversion: str
534
535
    :param appname: Application name, preferably like on server.
536
    :type appname: str
537
538
    :param appversion: Application version.
539
    :type appversion: str
540
541
    :param clean: Whether or not to clean up app name. Default is False.
542
    :type clean: bool
543
    """
544 5
    baseurl = create_base_url(softwareversion)
545 5
    if clean:
546 5
        appname = format_app_name(appname)
547 5
    return "{0}/{1}-{2}-nto+armle-v7+signed.bar".format(baseurl, appname, appversion)
548
549
550 5
def generate_urls(softwareversion, osversion, radioversion, core=False):
551
    """
552
    Generate a list of OS URLs and a list of radio URLs based on input.
553
554
    :param softwareversion: Software version to hash.
555
    :type softwareversion: str
556
557
    :param osversion: OS version.
558
    :type osversion: str
559
560
    :param radioversion: Radio version.
561
    :type radioversion: str
562
563
    :param core: Whether or not to return core URLs as well.
564
    :type core: bool
565
    """
566 5
    osurls = [
567
        create_bar_url(softwareversion, "winchester.factory_sfi.desktop", osversion),
568
        create_bar_url(softwareversion, "qc8960.factory_sfi.desktop", osversion),
569
        create_bar_url(softwareversion, "qc8960.factory_sfi.desktop", osversion),
570
        create_bar_url(softwareversion, "qc8974.factory_sfi.desktop", osversion)
571
    ]
572 5
    radiourls = [
573
        create_bar_url(softwareversion, "m5730", radioversion),
574
        create_bar_url(softwareversion, "qc8960", radioversion),
575
        create_bar_url(softwareversion, "qc8960.omadm", radioversion),
576
        create_bar_url(softwareversion, "qc8960.wtr", radioversion),
577
        create_bar_url(softwareversion, "qc8960.wtr5", radioversion),
578
        create_bar_url(softwareversion, "qc8930.wtr5", radioversion),
579
        create_bar_url(softwareversion, "qc8974.wtr2", radioversion)
580
    ]
581 5
    coreurls = []
582 5
    osurls, radiourls = filter_urls(osurls, radiourls, osversion)
583 5
    if core:
584 5
        coreurls = [x.replace(".desktop", "") for x in osurls]
585 5
    return osurls, radiourls, coreurls
586
587
588 5
def newer_103(splitos, third):
589
    """
590
    Return True if given split OS version is 10.3.X or newer.
591
592
    :param splitos: OS version, split on the dots: [10, 3, 3, 2205]
593
    :type: list(int)
594
595
    :param third: The X in 10.3.X.
596
    :type third: int
597
    """
598 5
    newer = True if ((splitos[1] >= 4) or (splitos[1] == 3 and splitos[2] >= third)) else False
599 5
    return newer
600
601
602 5
def filter_urls(osurls, radiourls, osversion):
603
    """
604
    Filter lists of OS and radio URLs.
605
606
    :param osurls: List of OS URLs.
607
    :type osurls: list(str)
608
609
    :param radiourls: List of radio URLs.
610
    :type radiourls: list(str)
611
612
    :param osversion: OS version.
613
    :type osversion: str
614
    """
615 5
    splitos = [int(i) for i in osversion.split(".")]
616 5
    osurls[2] = filter_1031(osurls[2], splitos, 5)  # Z3 10.3.1+
617 5
    osurls[3] = filter_1031(osurls[3], splitos, 6)  # Passport 10.3.1+
618 5
    osurls[0], radiourls[0] = pop_stl1(osurls[0], radiourls[0], splitos)  # STL100-1 10.3.3+
619 5
    return osurls, radiourls
620
621
622 5
def filter_1031(osurl, splitos, device):
623
    """
624
    Modify URLs to reflect changes in 10.3.1.
625
626
    :param osurl: OS URL to modify.
627
    :type osurl: str
628
629
    :param splitos: OS version, split and cast to int: [10, 3, 2, 2876]
630
    :type splitos: list(int)
631
632
    :param device: Device to use.
633
    :type device: int
634
    """
635 5
    if newer_103(splitos, 1):
636 5
        filterdict = {5: ("qc8960.factory_sfi", "qc8960.factory_sfi_hybrid_qc8x30"), 6: ("qc8974.factory_sfi", "qc8960.factory_sfi_hybrid_qc8974")}
637 5
        osurl = filter_osversion(osurl, device, filterdict)
638 5
    return osurl
639
640
641 5
def pop_stl1(osurl, radiourl, splitos):
642
    """
643
    Replace STL100-1 links in 10.3.3+.
644
645
    :param osurl: OS URL to modify.
646
    :type osurl: str
647
648
    :param radiourl: Radio URL to modify.
649
    :type radiourl: str
650
651
    :param splitos: OS version, split and cast to int: [10, 3, 3, 2205]
652
    :type splitos: list(int)
653
    """
654 5
    if newer_103(splitos, 3):
655 5
        osurl = osurl.replace("winchester", "qc8960")  # duplicates get filtered out later
656 5
        radiourl = radiourl.replace("m5730", "qc8960")
657 5
    return osurl, radiourl
658
659
660 5
def filter_osversion(osurl, device, filterdict):
661
    """
662
    Modify URLs based on device index and dictionary of changes.
663
664
    :param osurl: OS URL to modify.
665
    :type osurl: str
666
667
    :param device: Device to use.
668
    :type device: int
669
670
    :param filterdict: Dictionary of changes: {device : (before, after)}
671
    :type filterdict: dict(int:(str, str))
672
    """
673 5
    if device in filterdict.keys():
674 5
        osurl = osurl.replace(filterdict[device][0], filterdict[device][1])
675 5
    return osurl
676
677
678 5
def generate_lazy_urls(softwareversion, osversion, radioversion, device):
679
    """
680
    Generate a pair of OS/radio URLs based on input.
681
682
    :param softwareversion: Software version to hash.
683
    :type softwareversion: str
684
685
    :param osversion: OS version.
686
    :type osversion: str
687
688
    :param radioversion: Radio version.
689
    :type radioversion: str
690
691
    :param device: Device to use.
692
    :type device: int
693
    """
694 5
    splitos = [int(i) for i in osversion.split(".")]
695 5
    rads = ["m5730", "qc8960", "qc8960.omadm", "qc8960.wtr",
696
            "qc8960.wtr5", "qc8930.wtr4", "qc8974.wtr2"]
697 5
    oses = ["winchester.factory", "qc8960.factory", "qc8960.verizon",
698
            "qc8974.factory"]
699 5
    maps = {0:0, 1:1, 2:2, 3:1, 4:1, 5:1, 6:3}
700 5
    osurl = create_bar_url(softwareversion, "{0}_sfi.desktop".format(oses[maps[device]]), osversion)
701 5
    radiourl = create_bar_url(softwareversion, rads[device], radioversion)
702 5
    osurl = filter_1031(osurl, splitos, device)
703 5
    return osurl, radiourl
704
705
706 5
def bulk_urls(softwareversion, osversion, radioversion, core=False, altsw=None):
707
    """
708
    Generate all URLs, plus extra Verizon URLs.
709
710
    :param softwareversion: Software version to hash.
711
    :type softwareversion: str
712
713
    :param osversion: OS version.
714
    :type osversion: str
715
716
    :param radioversion: Radio version.
717
    :type radioversion: str
718
719
    :param device: Device to use.
720
    :type device: int
721
722
    :param core: Whether or not to return core URLs as well.
723
    :type core: bool
724
725
    :param altsw: Radio software release, if not the same as OS.
726
    :type altsw: str
727
    """
728 5
    baseurl = create_base_url(softwareversion)
729 5
    osurls, radurls, coreurls = generate_urls(softwareversion, osversion, radioversion, core)
730 5
    vzwos, vzwrad = generate_lazy_urls(softwareversion, osversion, radioversion, 2)
731 5
    osurls.append(vzwos)
732 5
    radurls.append(vzwrad)
733 5
    vzwcore = vzwos.replace("sfi.desktop", "sfi")
734 5
    if core:
735 5
        coreurls.append(vzwcore)
736 5
    osurls = list(set(osurls))  # pop duplicates
737 5
    radurls = list(set(radurls))
738 5
    if core:
739 5
        coreurls = list(set(coreurls))
740 5
    radurls = bulk_urls_altsw(radurls, baseurl, altsw)
741 5
    return osurls, coreurls, radurls
742
743
744 5
def bulk_urls_altsw(radurls, baseurl, altsw=None):
745
    """
746
    Handle alternate software release for radio.
747
748
    :param radurls: List of radio URLs.
749
    :type radurls: list(str)
750
751
    :param baseurl: Base URL (from http to hashed SW release).
752
    :type baseurl: str
753
754
    :param altsw: Radio software release, if not the same as OS.
755
    :type altsw: str
756
    """
757 5
    if altsw is not None:
758 5
        altbase = create_base_url(altsw)
759 5
        radiourls2 = [rad.replace(baseurl, altbase) for rad in radurls]
760 5
        radurls = radiourls2
761 5
        del radiourls2
762 5
    return radurls
763
764
765 5
def line_begin():
766
    """
767
    Go to beginning of line, to overwrite whatever's there.
768
    """
769 5
    sys.stdout.write("\r")
770 5
    sys.stdout.flush()
771
772
773 5
def spinner_clear():
774
    """
775
    Get rid of any spinner residue left in stdout.
776
    """
777 5
    sys.stdout.write("\b \b")
778 5
    sys.stdout.flush()
779
780
781 5
class Spinner(object):
782
    """
783
    A basic spinner using itertools. No need for progress.
784
    """
785
786 5
    def __init__(self):
787 5
        self.wheel = itertools.cycle(['-', '/', '|', '\\'])
788 5
        self.file = dummy.UselessStdout()
789
790 5
    def after(self):
791
        """
792
        Iterate over itertools.cycle, write to file.
793
        """
794 5
        try:
795 5
            self.file.write(next(self.wheel))
796 5
            self.file.flush()
797 5
            self.file.write("\b\r")
798 5
            self.file.flush()
799
        except (KeyboardInterrupt, SystemExit):
800
            self.stop()
801
802 5
    def stop(self):
803
        """
804
        Kill output.
805
        """
806 5
        self.file = dummy.UselessStdout()
807
808
809 5
class SpinManager(object):
810
    """
811
    Wraps around the itertools spinner, runs it in another thread.
812
    """
813
814 5
    def __init__(self):
815 5
        spinner = Spinner()
816 5
        self.spinner = spinner
817 5
        self.thread = threading.Thread(target=self.loop, args=())
818 5
        self.thread.daemon = True
819 5
        self.scanning = False
820 5
        self.spinner.file = dummy.UselessStdout()
821
822 5
    def start(self):
823
        """
824
        Begin the spinner.
825
        """
826 5
        self.spinner.file = sys.stderr
827 5
        self.scanning = True
828 5
        self.thread.start()
829
830 5
    def loop(self):
831
        """
832
        Spin if scanning, clean up if not.
833
        """
834 5
        while self.scanning:
835 5
            time.sleep(0.5)
836 5
            try:
837 5
                line_begin()
838 5
                self.spinner.after()
839
            except (KeyboardInterrupt, SystemExit):
840
                self.scanning = False
841
                self.stop()
842
843 5
    def stop(self):
844
        """
845
        Stop the spinner.
846
        """
847 5
        self.spinner.stop()
848 5
        self.scanning = False
849 5
        spinner_clear()
850 5
        line_begin()
851 5
        if not is_windows():
852 5
            print("\n")
853
854
855 5
def return_and_delete(target):
856
    """
857
    Read text file, then delete it. Return contents.
858
859
    :param target: Text file to read.
860
    :type target: str
861
    """
862 5
    with open(target, "r") as thefile:
863 5
        content = thefile.read()
864 5
    os.remove(target)
865 5
    return content
866
867
868 5
def verify_loader_integrity(loaderfile):
869
    """
870
    Test for created loader integrity. Windows-only.
871
872
    :param loaderfile: Path to loader.
873
    :type loaderfile: str
874
    """
875 5
    if not is_windows():
876 5
        pass
877
    else:
878 5
        excode = None
879 5
        try:
880 5
            with open(os.devnull, 'rb') as dnull:
881 5
                cmd = "{0} fileinfo".format(loaderfile)
882 5
                excode = subprocess.call(cmd, stdout=dnull, stderr=subprocess.STDOUT)
883 5
        except OSError:
884 5
            excode = -1
885 5
        return excode == 0  # 0 if OK, non-zero if something broke
886
887
888 5
def bulkfilter_printer(afile):
889
    """
890
    Print filename and verify a loader file.
891
892
    :param afile: Path to file.
893
    :type afile: str
894
    """
895 5
    print("TESTING: {0}".format(os.path.basename(afile)))
896 5
    if not verify_loader_integrity(afile):
897 5
        return os.path.basename(afile)
898
899
900 5
def bulkfilter(files):
901
    """
902
    Verify all loader files in a given list.
903
904
    :param files: List of files.
905
    :type files: list(str)
906
    """
907 5
    brokens = [bulkfilter_printer(file) for file in files if prepends(os.path.basename(file), bbconstants.PREFIXES, ".exe")]
908 5
    return brokens
909
910
911 5
def verify_bulk_loaders(ldir):
912
    """
913
    Run :func:`verify_loader_integrity` for all files in a dir.
914
915
    :param ldir: Directory to use.
916
    :type ldir: str
917
    """
918 5
    if not is_windows():
919 5
        pass
920
    else:
921 5
        files = verify_bulk_loaders_filefilter(ldir)
922 5
        brokens = verify_bulk_loaders_brokens(files)
923 5
        return brokens
924
925
926 5
def verify_bulk_loaders_filefilter(ldir):
927
    """
928
    Prepare file names for :func:`verify_bulk_loaders`.
929
930
    :param ldir: Directory to use.
931
    :type ldir: str
932
    """
933 5
    files = [os.path.join(ldir, file) for file in os.listdir(ldir) if not os.path.isdir(file)]
934 5
    return files
935
936
937 5
def verify_bulk_loaders_brokens(files):
938
    """
939
    Prepare filtered file list for :func:`verify_bulk_loaders`.
940
941
    :param files: List of files.
942
    :type files: list(str)
943
    """
944 5
    brokens = [file for file in bulkfilter(files) if file]
945 5
    return brokens
946
947
948 5
def list_workers(input_data, workerlimit):
949
    """
950
    Count number of threads, either length of iterable or provided limit.
951
952
    :param input_data: Input data, some iterable.
953
    :type input_data: list
954
955
    :param workerlimit: Maximum number of workers.
956
    :type workerlimit: int
957
    """
958 5
    runners = len(input_data) if len(input_data) < workerlimit else workerlimit
959 5
    return runners
960
961
962 5
def cpu_workers(input_data):
963
    """
964
    Count number of CPU workers, smaller of number of threads and length of data.
965
966
    :param input_data: Input data, some iterable.
967
    :type input_data: list
968
    """
969 5
    return list_workers(input_data, compat.enum_cpus())
970
971
972 5
def prep_logfile():
973
    """
974
    Prepare log file, labeling it with current date. Select folder based on frozen status.
975
    """
976 5
    logfile = "{0}.txt".format(time.strftime("%Y_%m_%d_%H%M%S"))
977 5
    basefolder = prep_logfile_folder()
978 5
    record = os.path.join(basefolder, logfile)
979 5
    open(record, "w").close()
980 5
    return record
981
982
983 5
def prep_logfile_folder():
984
    """
985
    Prepare folder to write log file to.
986
    """
987 5
    if getattr(sys, 'frozen', False):
988 5
        basefolder = os.path.join(os.getcwd(), "lookuplogs")
989 5
        os.makedirs(basefolder, exist_ok=True)
990
    else:
991 5
        basefolder = iniconfig.config_homepath(None, True)
992 5
    return basefolder
993
994
995 5
def prepends(file, pre, suf):
996
    """
997
    Check if filename starts with/ends with stuff.
998
999
    :param file: File to check.
1000
    :type file: str
1001
1002
    :param pre: Prefix(es) to check.
1003
    :type pre: str or list or tuple
1004
1005
    :param suf: Suffix(es) to check.
1006
    :type suf: str or list or tuple
1007
    """
1008 5
    return file.startswith(pre) and file.endswith(suf)
1009
1010
1011 5
def lprint(iterable):
1012
    """
1013
    A oneliner for 'for item in x: print item'.
1014
1015
    :param iterable: Iterable to print.
1016
    :type iterable: list/tuple
1017
    """
1018 5
    for item in iterable:
1019 5
        print(item)
1020
1021
1022 5
def cappath_config_loader(homepath=None):
1023
    """
1024
    Read a ConfigParser file to get cap preferences.
1025
1026
    :param homepath: Folder containing ini file. Default is user directory.
1027
    :type homepath: str
1028
    """
1029 5
    capini = iniconfig.generic_loader('cappath', homepath)
1030 5
    cappath = capini.get('path', fallback=bbconstants.CAP.location)
1031 5
    return cappath
1032
1033
1034 5
def cappath_config_writer(cappath=None, homepath=None):
1035
    """
1036
    Write a ConfigParser file to store cap preferences.
1037
1038
    :param cappath: Method to use.
1039
    :type cappath: str
1040
1041
    :param homepath: Folder containing ini file. Default is user directory.
1042
    :type homepath: str
1043
    """
1044 5
    cappath = grab_cap() if cappath is None else cappath
1045 5
    results = {"path": cappath}
1046 5
    iniconfig.generic_writer("cappath", results, homepath)
1047
1048
1049 5
def one_and_none(first, second):
1050
    """
1051
    Check if one element in a pair is None and one isn't.
1052
1053
    :param first: To return True, this must be None.
1054
    :type first: str
1055
1056
    :param second: To return True, this mustbe false.
1057
    :type second: str
1058
    """
1059 5
    sentinel = True if first is None and second is not None else False
1060 5
    return sentinel
1061
1062
1063 5
def def_args(dirs):
1064
    """
1065
    Return prepared argument list for most instances of :func:`cond_check:`.
1066
1067
    :param dirs: List of directories.
1068
    :type dirs: list(str)
1069
    """
1070 5
    return [dirs[4], dirs[5], dirs[2], dirs[3]]
1071
1072
1073 5
def cond_do(dofunc, goargs, restargs=None, condition=True):
1074
    """
1075
    Do a function, check a condition, then do same function but swap first argument.
1076
1077
    :param dofunc: Function to do.
1078
    :type dofunc: function
1079
1080
    :param goargs: List of variable arguments.
1081
    :type goargs: list(str)
1082
1083
    :param restargs: Rest of arguments, which are constant.
1084
    :type restargs: list(str)
1085
1086
    :param condition: Condition to check in order to use secondarg.
1087
    :type condition: bool
1088
    """
1089 5
    restargs = [] if restargs is None else restargs
1090 5
    dofunc(goargs[0], *restargs)
1091 5
    if condition:
1092 5
        dofunc(goargs[1], *restargs)
1093
1094
1095 5
def cond_check(dofunc, goargs, restargs=None, condition=True, checkif=True, checkifnot=True):
1096
    """
1097
    Do :func:`cond_do` based on a condition, then do it again based on a second condition.
1098
1099
    :param dofunc: Function to do.
1100
    :type dofunc: function
1101
1102
    :param goargs: List of variable arguments.
1103
    :type goargs: list(str)
1104
1105
    :param restargs: Rest of arguments, which are constant.
1106
    :type restargs: list(str)
1107
1108
    :param condition: Condition to check in order to use secondarg.
1109
    :type condition: bool
1110
1111
    :param checkif: Do :func:`cond_do` if this is True.
1112
    :type checkif: bool
1113
1114
    :param checkifnot: Do :func:`cond_do` if this is False.
1115
    :type checkifnot: bool
1116
    """
1117 5
    if checkif:
1118 5
        cond_do(dofunc, goargs[0:2], restargs, condition)
1119 5
    if not checkifnot:
1120
        cond_do(dofunc, goargs[2:4], restargs, condition)
1121