Passed
Push — master ( 604dcd...57fbcd )
by John
03:24
created

bbarchivist.utilities.valid_carrier()   A

Complexity

Conditions 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 8
nop 1
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
ccs 8
cts 8
cp 1
crap 4
1
#!/usr/bin/env python3
2 5
"""This module is used for miscellaneous utilities."""
3
4 5
import glob  # cap grabbing
5 5
import hashlib  # base url creation
6 5
import itertools  # spinners gonna spin
7 5
import os  # path work
8 5
import platform  # platform info
9 5
import subprocess  # loader verification
10 5
import sys  # streams, version info
11 5
import threading  # get thread for spinner
12 5
import time  # spinner delay
13
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 s2b(input_check):
128
    """
129
    Return Boolean interpretation of string input.
130
131
    :param input_check: String to check if it means True or False.
132
    :type input_check: str
133
    """
134 5
    return str(input_check).lower() in ("yes", "true", "t", "1", "y")
135
136
137 5
def i2b(input_check):
138
    """
139
    Return Boolean interpretation of typed input.
140
141
    :param input_check: Query to feed into input function.
142
    :type input_check: str
143
    """
144 5
    return s2b(input(input_check))
145
146
147 5
def is_amd64():
148
    """
149
    Check if script is running on an AMD64 system (Python can be 32/64, this is for subprocess)
150
    """
151 5
    return platform.machine().endswith("64")
152
153
154 5
def is_windows():
155
    """
156
    Check if script is running on Windows.
157
    """
158 5
    return platform.system() == "Windows"
159
160
161 5
def talkaprint(msg, talkative=False):
162
    """
163
    Print only if asked to.
164
165
    :param msg: Message to print.
166
    :type msg: str
167
168
    :param talkative: Whether to output to screen. False by default.
169
    :type talkative: bool
170
    """
171 5
    if talkative:
172 5
        print(msg)
173
174
175 5
def get_seven_zip(talkative=False):
176
    """
177
    Return name of 7-Zip executable.
178
    On POSIX, it MUST be 7za.
179
    On Windows, it can be installed or supplied with the script.
180
    :func:`win_seven_zip` is used to determine if it's installed.
181
182
    :param talkative: Whether to output to screen. False by default.
183
    :type talkative: bool
184
    """
185 5
    return win_seven_zip(talkative) if is_windows() else "7za"
186
187
188 5
def win_seven_zip(talkative=False):
189
    """
190
    For Windows, check where 7-Zip is ("where", pretty much).
191
    Consult registry first for any installed instances of 7-Zip.
192
193
    :param talkative: Whether to output to screen. False by default.
194
    :type talkative: bool
195
    """
196 5
    talkaprint("CHECKING INSTALLED FILES...", talkative)
197 5
    try:
198 5
        path = wsz_registry()
199 5
    except OSError as exc:
200 5
        if talkative:
201 5
            exceptions.handle_exception(exc, xit=None)
202 5
        talkaprint("TRYING LOCAL FILES...", talkative)
203 5
        return win_seven_zip_local(talkative)
204
    else:
205
        talkaprint("7ZIP USING INSTALLED FILES", talkative)
206
        return '"{0}"'.format(os.path.join(path[0], "7z.exe"))
207
208
209 5
def wsz_registry():
210
    """
211
    Check Windows registry for 7-Zip executable location.
212
    """
213
    import winreg  # windows registry
214
    hk7z = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\7-Zip")
215
    path = winreg.QueryValueEx(hk7z, "Path")
216
    return path
217
218
219 5
def win_seven_zip_local(talkative=False):
220
    """
221
    If 7-Zip isn't in the registry, fall back onto supplied executables.
222
    If *those* aren't there, return "error".
223
224
    :param talkative: Whether to output to screen. False by default.
225
    :type talkative: bool
226
    """
227 5
    filecount = wsz_filecount()
228 5
    if filecount == 2:
229 5
        szexe = wsz_local_good(talkative)
230
    else:
231 5
        szexe = wsz_local_bad(talkative)
232 5
    return szexe
233
234
235 5
def wsz_filecount():
236
    """
237
    Get count of 7-Zip executables in local folder.
238
    """
239 5
    filecount = len([x for x in os.listdir(os.getcwd()) if x in ["7za.exe", "7za64.exe"]])
240 5
    return filecount
241
242
243 5
def wsz_local_good(talkative=False):
244
    """
245
    Get 7-Zip exe name if everything is good.
246
247
    :param talkative: Whether to output to screen. False by default.
248
    :type talkative: bool
249
    """
250 5
    talkaprint("7ZIP USING LOCAL FILES", talkative)
251 5
    szexe = "7za64.exe" if is_amd64() else "7za.exe"
252 5
    return szexe
253
254
255 5
def wsz_local_bad(talkative=False):
256
    """
257
    Handle 7-Zip exe name in case of issues.
258
259
    :param talkative: Whether to output to screen. False by default.
260
    :type talkative: bool
261
    """
262 5
    talkaprint("NO LOCAL FILES", talkative)
263 5
    szexe = "error"
264 5
    return szexe
265
266
267 5
def get_core_count():
268
    """
269
    Find out how many CPU cores this system has.
270
    """
271 5
    try:
272 5
        cores = str(compat.enum_cpus())  # 3.4 and up
273 5
    except NotImplementedError:
274 5
        cores = "1"  # 3.2-3.3
275
    else:
276 5
        if compat.enum_cpus() is None:
277 5
            cores = "1"
278 5
    return cores
279
280
281 5
def prep_seven_zip_path(path, talkative=False):
282
    """
283
    Print p7zip path on POSIX, or notify if not there.
284
285
    :param path: Path to use.
286
    :type path: str
287
288
    :param talkative: Whether to output to screen. False by default.
289
    :type talkative: bool
290
    """
291 5
    if path is None:
292 5
        talkaprint("NO 7ZIP\nPLEASE INSTALL p7zip", talkative)
293 5
        sentinel = False
294
    else:
295 5
        talkaprint("7ZIP FOUND AT {0}".format(path), talkative)
296 5
        sentinel = True
297 5
    return sentinel
298
299
300 5
def prep_seven_zip_posix(talkative=False):
301
    """
302
    Check for p7zip on POSIX.
303
304
    :param talkative: Whether to output to screen. False by default.
305
    :type talkative: bool
306
    """
307 5
    try:
308 5
        path = compat.where_which("7za")
309 5
    except ImportError:
310 5
        talkaprint("PLEASE INSTALL SHUTILWHICH WITH PIP", talkative)
311 5
        return False
312
    else:
313 5
        return prep_seven_zip_path(path, talkative)
314
315
316 5
def prep_seven_zip(talkative=False):
317
    """
318
    Check for presence of 7-Zip.
319
    On POSIX, check for p7zip.
320
    On Windows, check for 7-Zip.
321
322
    :param talkative: Whether to output to screen. False by default.
323
    :type talkative: bool
324
    """
325 5
    if is_windows():
326
        final = get_seven_zip(talkative) != "error"
327
    else:
328 5
        final = prep_seven_zip_posix(talkative)
329 5
    return final
330
331
332 5
def increment(version, inc=3):
333
    """
334
    Increment version by given number. For repeated lookups.
335
336
    :param version: w.x.y.ZZZZ, becomes w.x.y.(ZZZZ + increment).
337
    :type version: str
338
339
    :param inc: What to increment by. Default is 3.
340
    :type inc: str
341
    """
342 5
    splitos = version.split(".")
343 5
    splitos[3] = int(splitos[3])
344 5
    if splitos[3] > 9996:  # prevent overflow
345 5
        splitos[3] = 0
346 5
    splitos[3] += int(inc)
347 5
    splitos[3] = str(splitos[3])
348 5
    return ".".join(splitos)
349
350
351 5
def stripper(name):
352
    """
353
    Strip fluff from bar filename.
354
355
    :param name: Bar filename, must contain '-nto+armle-v7+signed.bar'.
356
    :type name: str
357
    """
358 5
    return name.replace("-nto+armle-v7+signed.bar", "")
359
360
361 5
def create_base_url(softwareversion):
362
    """
363
    Make the root URL for production server files.
364
365
    :param softwareversion: Software version to hash.
366
    :type softwareversion: str
367
    """
368
    # Hash software version
369 5
    swhash = hashlib.sha1(softwareversion.encode('utf-8'))
370 5
    hashedsoftwareversion = swhash.hexdigest()
371
    # Root of all urls
372 5
    baseurl = "http://cdn.fs.sl.blackberry.com/fs/qnx/production/{0}".format(hashedsoftwareversion)
373 5
    return baseurl
374
375
376 5
def format_app_name(appname):
377
    """
378
    Convert long reverse DNS name to short name.
379
380
    :param appname: Application name (ex. sys.pim.calendar -> "calendar")
381
    :type appname: str
382
    """
383 5
    final = appname.split(".")[-1]
384 5
    return final
385
386
387 5
def create_bar_url(softwareversion, appname, appversion, clean=False):
388
    """
389
    Make the URL for any production server file.
390
391
    :param softwareversion: Software version to hash.
392
    :type softwareversion: str
393
394
    :param appname: Application name, preferably like on server.
395
    :type appname: str
396
397
    :param appversion: Application version.
398
    :type appversion: str
399
400
    :param clean: Whether or not to clean up app name. Default is False.
401
    :type clean: bool
402
    """
403 5
    baseurl = create_base_url(softwareversion)
404 5
    if clean:
405 5
        appname = format_app_name(appname)
406 5
    return "{0}/{1}-{2}-nto+armle-v7+signed.bar".format(baseurl, appname, appversion)
407
408
409 5
def generate_urls(softwareversion, osversion, radioversion, core=False):
410
    """
411
    Generate a list of OS URLs and a list of radio URLs based on input.
412
413
    :param softwareversion: Software version to hash.
414
    :type softwareversion: str
415
416
    :param osversion: OS version.
417
    :type osversion: str
418
419
    :param radioversion: Radio version.
420
    :type radioversion: str
421
422
    :param core: Whether or not to return core URLs as well.
423
    :type core: bool
424
    """
425 5
    osurls = [
426
        create_bar_url(softwareversion, "winchester.factory_sfi.desktop", osversion),
427
        create_bar_url(softwareversion, "qc8960.factory_sfi.desktop", osversion),
428
        create_bar_url(softwareversion, "qc8960.factory_sfi.desktop", osversion),
429
        create_bar_url(softwareversion, "qc8974.factory_sfi.desktop", osversion)
430
    ]
431 5
    radiourls = [
432
        create_bar_url(softwareversion, "m5730", radioversion),
433
        create_bar_url(softwareversion, "qc8960", radioversion),
434
        create_bar_url(softwareversion, "qc8960.omadm", radioversion),
435
        create_bar_url(softwareversion, "qc8960.wtr", radioversion),
436
        create_bar_url(softwareversion, "qc8960.wtr5", radioversion),
437
        create_bar_url(softwareversion, "qc8930.wtr5", radioversion),
438
        create_bar_url(softwareversion, "qc8974.wtr2", radioversion)
439
    ]
440 5
    coreurls = []
441 5
    osurls, radiourls = filter_urls(osurls, radiourls, osversion)
442 5
    if core:
443 5
        coreurls = [x.replace(".desktop", "") for x in osurls]
444 5
    return osurls, radiourls, coreurls
445
446
447 5
def newer_103(splitos, third):
448
    """
449
    Return True if given split OS version is 10.3.X or newer.
450
451
    :param splitos: OS version, split on the dots: [10, 3, 3, 2205]
452
    :type: list(int)
453
454
    :param third: The X in 10.3.X.
455
    :type third: int
456
    """
457 5
    newer = True if ((splitos[1] >= 4) or (splitos[1] == 3 and splitos[2] >= third)) else False
458 5
    return newer
459
460
461 5
def filter_urls(osurls, radiourls, osversion):
462
    """
463
    Filter lists of OS and radio URLs.
464
465
    :param osurls: List of OS URLs.
466
    :type osurls: list(str)
467
468
    :param radiourls: List of radio URLs.
469
    :type radiourls: list(str)
470
471
    :param osversion: OS version.
472
    :type osversion: str
473
    """
474 5
    splitos = [int(i) for i in osversion.split(".")]
475 5
    osurls[2] = filter_1031(osurls[2], splitos, 5)  # Z3 10.3.1+
476 5
    osurls[3] = filter_1031(osurls[3], splitos, 6)  # Passport 10.3.1+
477 5
    osurls[0], radiourls[0] = pop_stl1(osurls[0], radiourls[0], splitos)  # STL100-1 10.3.3+
478 5
    return osurls, radiourls
479
480
481 5
def filter_1031(osurl, splitos, device):
482
    """
483
    Modify URLs to reflect changes in 10.3.1.
484
485
    :param osurl: OS URL to modify.
486
    :type osurl: str
487
488
    :param splitos: OS version, split and cast to int: [10, 3, 2, 2876]
489
    :type splitos: list(int)
490
491
    :param device: Device to use.
492
    :type device: int
493
    """
494 5
    if newer_103(splitos, 1):
495 5
        filterdict = {5: ("qc8960.factory_sfi", "qc8960.factory_sfi_hybrid_qc8x30"), 6: ("qc8974.factory_sfi", "qc8960.factory_sfi_hybrid_qc8974")}
496 5
        osurl = filter_osversion(osurl, device, filterdict)
497 5
    return osurl
498
499
500 5
def pop_stl1(osurl, radiourl, splitos):
501
    """
502
    Replace STL100-1 links in 10.3.3+.
503
504
    :param osurl: OS URL to modify.
505
    :type osurl: str
506
507
    :param radiourl: Radio URL to modify.
508
    :type radiourl: str
509
510
    :param splitos: OS version, split and cast to int: [10, 3, 3, 2205]
511
    :type splitos: list(int)
512
    """
513 5
    if newer_103(splitos, 3):
514 5
        osurl = osurl.replace("winchester", "qc8960")  # duplicates get filtered out later
515 5
        radiourl = radiourl.replace("m5730", "qc8960")
516 5
    return osurl, radiourl
517
518
519 5
def filter_osversion(osurl, device, filterdict):
520
    """
521
    Modify URLs based on device index and dictionary of changes.
522
523
    :param osurl: OS URL to modify.
524
    :type osurl: str
525
526
    :param device: Device to use.
527
    :type device: int
528
529
    :param filterdict: Dictionary of changes: {device : (before, after)}
530
    :type filterdict: dict(int:(str, str))
531
    """
532 5
    if device in filterdict.keys():
533 5
        osurl = osurl.replace(filterdict[device][0], filterdict[device][1])
534 5
    return osurl
535
536
537 5
def generate_lazy_urls(softwareversion, osversion, radioversion, device):
538
    """
539
    Generate a pair of OS/radio URLs based on input.
540
541
    :param softwareversion: Software version to hash.
542
    :type softwareversion: str
543
544
    :param osversion: OS version.
545
    :type osversion: str
546
547
    :param radioversion: Radio version.
548
    :type radioversion: str
549
550
    :param device: Device to use.
551
    :type device: int
552
    """
553 5
    splitos = [int(i) for i in osversion.split(".")]
554 5
    rads = ["m5730", "qc8960", "qc8960.omadm", "qc8960.wtr",
555
            "qc8960.wtr5", "qc8930.wtr4", "qc8974.wtr2"]
556 5
    oses = ["winchester.factory", "qc8960.factory", "qc8960.verizon",
557
            "qc8974.factory"]
558 5
    maps = {0:0, 1:1, 2:2, 3:1, 4:1, 5:1, 6:3}
559 5
    osurl = create_bar_url(softwareversion, "{0}_sfi.desktop".format(oses[maps[device]]), osversion)
560 5
    radiourl = create_bar_url(softwareversion, rads[device], radioversion)
561 5
    osurl = filter_1031(osurl, splitos, device)
562 5
    return osurl, radiourl
563
564
565 5
def bulk_urls(softwareversion, osversion, radioversion, core=False, altsw=None):
566
    """
567
    Generate all URLs, plus extra Verizon URLs.
568
569
    :param softwareversion: Software version to hash.
570
    :type softwareversion: str
571
572
    :param osversion: OS version.
573
    :type osversion: str
574
575
    :param radioversion: Radio version.
576
    :type radioversion: str
577
578
    :param device: Device to use.
579
    :type device: int
580
581
    :param core: Whether or not to return core URLs as well.
582
    :type core: bool
583
584
    :param altsw: Radio software release, if not the same as OS.
585
    :type altsw: str
586
    """
587 5
    baseurl = create_base_url(softwareversion)
588 5
    osurls, radurls, coreurls = generate_urls(softwareversion, osversion, radioversion, core)
589 5
    vzwos, vzwrad = generate_lazy_urls(softwareversion, osversion, radioversion, 2)
590 5
    osurls.append(vzwos)
591 5
    radurls.append(vzwrad)
592 5
    vzwcore = vzwos.replace("sfi.desktop", "sfi")
593 5
    if core:
594 5
        coreurls.append(vzwcore)
595 5
    osurls = list(set(osurls))  # pop duplicates
596 5
    radurls = list(set(radurls))
597 5
    if core:
598 5
        coreurls = list(set(coreurls))
599 5
    radurls = bulk_urls_altsw(radurls, baseurl, altsw)
600 5
    return osurls, coreurls, radurls
601
602
603 5
def bulk_urls_altsw(radurls, baseurl, altsw=None):
604
    """
605
    Handle alternate software release for radio.
606
607
    :param radurls: List of radio URLs.
608
    :type radurls: list(str)
609
610
    :param baseurl: Base URL (from http to hashed SW release).
611
    :type baseurl: str
612
613
    :param altsw: Radio software release, if not the same as OS.
614
    :type altsw: str
615
    """
616 5
    if altsw is not None:
617 5
        altbase = create_base_url(altsw)
618 5
        radiourls2 = [rad.replace(baseurl, altbase) for rad in radurls]
619 5
        radurls = radiourls2
620 5
        del radiourls2
621 5
    return radurls
622
623
624 5
def line_begin():
625
    """
626
    Go to beginning of line, to overwrite whatever's there.
627
    """
628 5
    sys.stdout.write("\r")
629 5
    sys.stdout.flush()
630
631
632 5
def spinner_clear():
633
    """
634
    Get rid of any spinner residue left in stdout.
635
    """
636 5
    sys.stdout.write("\b \b")
637 5
    sys.stdout.flush()
638
639
640 5
class Spinner(object):
0 ignored issues
show
Unused Code introduced by
The variable __class__ seems to be unused.
Loading history...
641
    """
642
    A basic spinner using itertools. No need for progress.
643
    """
644
645 5
    def __init__(self):
646
        """
647
        Generate the itertools wheel.
648
        """
649 5
        self.wheel = itertools.cycle(['-', '/', '|', '\\'])
650 5
        self.file = dummy.UselessStdout()
651
652 5
    def after(self):
653
        """
654
        Iterate over itertools.cycle, write to file.
655
        """
656 5
        try:
657 5
            self.file.write(next(self.wheel))
658 5
            self.file.flush()
659 5
            self.file.write("\b\r")
660 5
            self.file.flush()
661
        except (KeyboardInterrupt, SystemExit):
662
            self.stop()
663
664 5
    def stop(self):
665
        """
666
        Kill output.
667
        """
668 5
        self.file = dummy.UselessStdout()
669
670
671 5
class SpinManager(object):
0 ignored issues
show
Unused Code introduced by
The variable __class__ seems to be unused.
Loading history...
672
    """
673
    Wraps around the itertools spinner, runs it in another thread.
674
    """
675
676 5
    def __init__(self):
677
        """
678
        Start the spinner thread.
679
        """
680 5
        spinner = Spinner()
681 5
        self.spinner = spinner
682 5
        self.thread = threading.Thread(target=self.loop, args=())
683 5
        self.thread.daemon = True
684 5
        self.scanning = False
685 5
        self.spinner.file = dummy.UselessStdout()
686
687 5
    def start(self):
688
        """
689
        Begin the spinner.
690
        """
691 5
        self.spinner.file = sys.stderr
692 5
        self.scanning = True
693 5
        self.thread.start()
694
695 5
    def loop(self):
696
        """
697
        Spin if scanning, clean up if not.
698
        """
699 5
        while self.scanning:
700 5
            time.sleep(0.5)
701 5
            try:
702 5
                line_begin()
703 5
                self.spinner.after()
704
            except (KeyboardInterrupt, SystemExit):
705
                self.scanning = False
706
                self.stop()
707
708 5
    def stop(self):
709
        """
710
        Stop the spinner.
711
        """
712 5
        self.spinner.stop()
713 5
        self.scanning = False
714 5
        spinner_clear()
715 5
        line_begin()
716 5
        if not is_windows():
717 5
            print("\n")
718
719
720 5
def return_and_delete(target):
721
    """
722
    Read text file, then delete it. Return contents.
723
724
    :param target: Text file to read.
725
    :type target: str
726
    """
727 5
    with open(target, "r") as thefile:
728 5
        content = thefile.read()
729 5
    os.remove(target)
730 5
    return content
731
732
733 5
def verify_loader_integrity(loaderfile):
734
    """
735
    Test for created loader integrity. Windows-only.
736
737
    :param loaderfile: Path to loader.
738
    :type loaderfile: str
739
    """
740 5
    if not is_windows():
741 5
        pass
742
    else:
743 5
        excode = None
744 5
        try:
745 5
            with open(os.devnull, 'rb') as dnull:
746 5
                cmd = "{0} fileinfo".format(loaderfile)
747 5
                excode = subprocess.call(cmd, stdout=dnull, stderr=subprocess.STDOUT)
748 5
        except OSError:
749 5
            excode = -1
750 5
        return excode == 0  # 0 if OK, non-zero if something broke
751
752
753 5
def bulkfilter_printer(afile):
754
    """
755
    Print filename and verify a loader file.
756
757
    :param afile: Path to file.
758
    :type afile: str
759
    """
760 5
    print("TESTING: {0}".format(os.path.basename(afile)))
761 5
    if not verify_loader_integrity(afile):
762 5
        return os.path.basename(afile)
763
764
765 5
def bulkfilter(files):
766
    """
767
    Verify all loader files in a given list.
768
769
    :param files: List of files.
770
    :type files: list(str)
771
    """
772 5
    brokens = [bulkfilter_printer(file) for file in files if prepends(os.path.basename(file), bbconstants.PREFIXES, ".exe")]
773 5
    return brokens
774
775
776 5
def verify_bulk_loaders(ldir):
777
    """
778
    Run :func:`verify_loader_integrity` for all files in a dir.
779
780
    :param ldir: Directory to use.
781
    :type ldir: str
782
    """
783 5
    if not is_windows():
784 5
        pass
785
    else:
786 5
        files = verify_bulk_loaders_filefilter(ldir)
787 5
        brokens = verify_bulk_loaders_brokens(files)
788 5
        return brokens
789
790
791 5
def verify_bulk_loaders_filefilter(ldir):
792
    """
793
    Prepare file names for :func:`verify_bulk_loaders`.
794
795
    :param ldir: Directory to use.
796
    :type ldir: str
797
    """
798 5
    files = [os.path.join(ldir, file) for file in os.listdir(ldir) if not os.path.isdir(file)]
799 5
    return files
800
801
802 5
def verify_bulk_loaders_brokens(files):
803
    """
804
    Prepare filtered file list for :func:`verify_bulk_loaders`.
805
806
    :param files: List of files.
807
    :type files: list(str)
808
    """
809 5
    brokens = [file for file in bulkfilter(files) if file]
810 5
    return brokens
811
812
813 5
def list_workers(input_data, workerlimit):
814
    """
815
    Count number of threads, either length of iterable or provided limit.
816
817
    :param input_data: Input data, some iterable.
818
    :type input_data: list
819
820
    :param workerlimit: Maximum number of workers.
821
    :type workerlimit: int
822
    """
823 5
    runners = len(input_data) if len(input_data) < workerlimit else workerlimit
824 5
    return runners
825
826
827 5
def cpu_workers(input_data):
828
    """
829
    Count number of CPU workers, smaller of number of threads and length of data.
830
831
    :param input_data: Input data, some iterable.
832
    :type input_data: list
833
    """
834 5
    return list_workers(input_data, compat.enum_cpus())
835
836
837 5
def prep_logfile():
838
    """
839
    Prepare log file, labeling it with current date. Select folder based on frozen status.
840
    """
841 5
    logfile = "{0}.txt".format(time.strftime("%Y_%m_%d_%H%M%S"))
842 5
    basefolder = prep_logfile_folder()
843 5
    record = os.path.join(basefolder, logfile)
844 5
    open(record, "w").close()
845 5
    return record
846
847
848 5
def prep_logfile_folder():
849
    """
850
    Prepare folder to write log file to.
851
    """
852 5
    if getattr(sys, 'frozen', False):
853 5
        basefolder = os.path.join(os.getcwd(), "lookuplogs")
854 5
        os.makedirs(basefolder, exist_ok=True)
855
    else:
856 5
        basefolder = iniconfig.config_homepath(None, True)
857 5
    return basefolder
858
859
860 5
def prepends(file, pre, suf):
861
    """
862
    Check if filename starts with/ends with stuff.
863
864
    :param file: File to check.
865
    :type file: str
866
867
    :param pre: Prefix(es) to check.
868
    :type pre: str or list or tuple
869
870
    :param suf: Suffix(es) to check.
871
    :type suf: str or list or tuple
872
    """
873 5
    return file.startswith(pre) and file.endswith(suf)
874
875
876 5
def lprint(iterable):
877
    """
878
    A oneliner for 'for item in x: print item'.
879
880
    :param iterable: Iterable to print.
881
    :type iterable: list/tuple
882
    """
883 5
    for item in iterable:
884 5
        print(item)
885
886
887 5
def cappath_config_loader(homepath=None):
888
    """
889
    Read a ConfigParser file to get cap preferences.
890
891
    :param homepath: Folder containing ini file. Default is user directory.
892
    :type homepath: str
893
    """
894 5
    capini = iniconfig.generic_loader('cappath', homepath)
895 5
    cappath = capini.get('path', fallback=bbconstants.CAP.location)
896 5
    return cappath
897
898
899 5
def cappath_config_writer(cappath=None, homepath=None):
900
    """
901
    Write a ConfigParser file to store cap preferences.
902
903
    :param cappath: Method to use.
904
    :type cappath: str
905
906
    :param homepath: Folder containing ini file. Default is user directory.
907
    :type homepath: str
908
    """
909 5
    cappath = grab_cap() if cappath is None else cappath
910 5
    results = {"path": cappath}
911 5
    iniconfig.generic_writer("cappath", results, homepath)
912
913
914 5
def one_and_none(first, second):
915
    """
916
    Check if one element in a pair is None and one isn't.
917
918
    :param first: To return True, this must be None.
919
    :type first: str
920
921
    :param second: To return True, this mustbe false.
922
    :type second: str
923
    """
924 5
    sentinel = True if first is None and second is not None else False
925 5
    return sentinel
926
927
928 5
def def_args(dirs):
929
    """
930
    Return prepared argument list for most instances of :func:`cond_check:`.
931
932
    :param dirs: List of directories.
933
    :type dirs: list(str)
934
    """
935 5
    return [dirs[4], dirs[5], dirs[2], dirs[3]]
936
937
938 5
def cond_do(dofunc, goargs, restargs=None, condition=True):
939
    """
940
    Do a function, check a condition, then do same function but swap first argument.
941
942
    :param dofunc: Function to do.
943
    :type dofunc: function
944
945
    :param goargs: List of variable arguments.
946
    :type goargs: list(str)
947
948
    :param restargs: Rest of arguments, which are constant.
949
    :type restargs: list(str)
950
951
    :param condition: Condition to check in order to use secondarg.
952
    :type condition: bool
953
    """
954 5
    restargs = [] if restargs is None else restargs
955 5
    dofunc(goargs[0], *restargs)
956 5
    if condition:
957 5
        dofunc(goargs[1], *restargs)
958
959
960 5
def cond_check(dofunc, goargs, restargs=None, condition=True, checkif=True, checkifnot=True):
961
    """
962
    Do :func:`cond_do` based on a condition, then do it again based on a second condition.
963
964
    :param dofunc: Function to do.
965
    :type dofunc: function
966
967
    :param goargs: List of variable arguments.
968
    :type goargs: list(str)
969
970
    :param restargs: Rest of arguments, which are constant.
971
    :type restargs: list(str)
972
973
    :param condition: Condition to check in order to use secondarg.
974
    :type condition: bool
975
976
    :param checkif: Do :func:`cond_do` if this is True.
977
    :type checkif: bool
978
979
    :param checkifnot: Do :func:`cond_do` if this is False.
980
    :type checkifnot: bool
981
    """
982 5
    if checkif:
983 5
        cond_do(dofunc, goargs[0:2], restargs, condition)
984 5
    if not checkifnot:
985
        cond_do(dofunc, goargs[2:4], restargs, condition)
986