Completed
Push — master ( 6133d7...e65795 )
by John
02:37
created

grab_datafile()   A

Complexity

Conditions 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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