Completed
Push — master ( 675895...a1bc32 )
by John
01:18
created

new_enough()   A

Complexity

Conditions 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
c 0
b 0
f 0
dl 0
loc 8
rs 9.4285
1
#!/usr/bin/env python3
2
"""This module is used for miscellaneous utilities."""
3
4
import os  # path work
5
import argparse  # argument parser for filters
6
import platform  # platform info
7
import glob  # cap grabbing
8
import configparser  # config parsing, duh
9
import threading  # get thread for spinner
10
import time  # spinner delay
11
import sys  # streams, version info
12
import itertools  # spinners gonna spin
13
import subprocess  # loader verification
14
import math  # rounding
0 ignored issues
show
Unused Code introduced by
The import math seems to be unused.
Loading history...
15
from bbarchivist import bbconstants  # cap location, version, filename bits
16
from bbarchivist import compat  # backwards compat
17
from bbarchivist import dummy  # useless stdout
18
19
__author__ = "Thurask"
20
__license__ = "WTFPL v2"
21
__copyright__ = "Copyright 2015-2016 Thurask"
22
23
24
def grab_cap():
25
    """
26
    Figure out where cap is, local, specified or system-supplied.
27
    """
28
    try:
29
        capfile = glob.glob(os.path.join(os.getcwd(), bbconstants.CAP.filename))[0]
30
    except IndexError:
31
        try:
32
            cappath = cappath_config_loader()
33
            capfile = glob.glob(cappath)[0]
34
        except IndexError:
35
            cappath_config_writer(bbconstants.CAP.location)
36
            return bbconstants.CAP.location  # no ini cap
37
        else:
38
            cappath_config_writer(os.path.abspath(capfile))
39
            return os.path.abspath(capfile)  # ini cap
40
    else:
41
        return os.path.abspath(capfile)  # local cap
42
43
44
def grab_cfp():
45
    """
46
    Figure out where cfp is, local or system-supplied.
47
    """
48
    try:
49
        cfpfile = glob.glob(os.path.join(os.getcwd(), bbconstants.CFP.filename))[0]
50
    except IndexError:
51
        cfpfile = bbconstants.CFP.location  # system cfp
52
    return os.path.abspath(cfpfile)  # local cfp
53
54
55
def new_enough(minver):
56
    """
57
    Check if we're at or above a minimum Python version.
58
59
    :param minver: What we should be equal or greater to.
60
    :type minver: int
61
    """
62
    return True if minver >= sys.version_info[1] else False
63
64
65
def fsizer(file_size):
66
    """
67
    Raw byte file size to human-readable string.
68
69
    :param file_size: Number to parse.
70
    :type file_size: float
71
    """
72
    if file_size is None:
73
        file_size = 0
74
    fsize = float(file_size)
75
    for sfix in ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB']:
76
        if fsize < 1024.0:
77
            size = "{0:3.2f}{1}".format(fsize, sfix)
78
            break
79
        else:
80
            fsize /= 1024.0
81
    else:
82
        size = "{0:3.2f}{1}".format(fsize, 'YB')
83
    return size
84
85
86
def signed_file_args(files):
87
    """
88
    Check if there are between 1 and 6 files supplied to argparse.
89
90
    :param files: List of signed files, between 1 and 6 strings.
91
    :type files: list(str)
92
    """
93
    filelist = [file for file in files if file]
94
    if not 1 <= len(filelist) <= 6:
95
        raise argparse.ArgumentError(argument=None, message="Requires 1-6 signed files")
96
    return files
97
98
99
def file_exists(file):
100
    """
101
    Check if file exists, raise argparse error if it doesn't.
102
103
    :param file: Path to a file, including extension.
104
    :type file: str
105
    """
106
    if not os.path.exists(file):
107
        raise argparse.ArgumentError(argument=None, message="{0} not found.".format(file))
108
    return file
109
110
111
def positive_integer(input_int):
112
    """
113
    Check if number > 0, raise argparse error if it isn't.
114
115
    :param input_int: Integer to check.
116
    :type input_int: str
117
    """
118
    if int(input_int) <= 0:
119
        info = "{0} is not >=0.".format(str(input_int))
120
        raise argparse.ArgumentError(argument=None, message=info)
121
    return int(input_int)
122
123
124
def valid_method(method):
125
    """
126
    Check if compression method is valid, raise argparse error if it isn't.
127
128
    :param method: Compression method to check.
129
    :type method: str
130
    """
131
    methodlist = bbconstants.METHODS
132
    if not new_enough(3):
133
        methodlist = [x for x in bbconstants.METHODS if x != "txz"]
134
    if method not in methodlist:
135
        info = "Invalid method {0}.".format(method)
136
        raise argparse.ArgumentError(argument=None, message=info)
137
    return method
138
139
140
def valid_carrier(mcc_mnc):
141
    """
142
    Check if MCC/MNC is valid (1-3 chars), raise argparse error if it isn't.
143
144
    :param mcc_mnc: MCC/MNC to check.
145
    :type mcc_mnc: str
146
    """
147
    if not str(mcc_mnc).isdecimal():
148
        infod = "Non-integer {0}.".format(str(mcc_mnc))
149
        raise argparse.ArgumentError(argument=None, message=infod)
150
    if len(str(mcc_mnc)) > 3 or len(str(mcc_mnc)) == 0:
151
        infol = "{0} is invalid.".format(str(mcc_mnc))
152
        raise argparse.ArgumentError(argument=None, message=infol)
153
    else:
154
        return mcc_mnc
155
156
157
def escreens_pin(pin):
158
    """
159
    Check if given PIN is valid, raise argparse error if it isn't.
160
161
    :param pin: PIN to check.
162
    :type pin: str
163
    """
164
    if len(pin) == 8:
165
        try:
166
            int(pin, 16)  # hexadecimal-ness
167
        except ValueError:
168
            raise argparse.ArgumentError(argument=None, message="Invalid PIN.")
169
        else:
170
            return pin.lower()
171
    else:
172
        raise argparse.ArgumentError(argument=None, message="Invalid PIN.")
173
174
175
def escreens_duration(duration):
176
    """
177
    Check if Engineering Screens duration is valid.
178
179
    :param duration: Duration to check.
180
    :type duration: int
181
    """
182
    if int(duration) in (1, 3, 6, 15, 30):
183
        return int(duration)
184
    else:
185
        raise argparse.ArgumentError(argument=None, message="Invalid duration.")
186
187
188
def privlookup_hashtype(method):
189
    """
190
    Check if Priv autoloader lookup hash type is valid.
191
192
    :param method: None for regular OS links, "sha256/512" for SHA256 or 512 hash.
193
    :type method: str
194
    """
195
    if method.lower() in ("sha512", "sha256"):
196
        return method.lower()
197
    else:
198
        raise argparse.ArgumentError(argument=None, message="Invalid type.")
199
200
201
def s2b(input_check):
202
    """
203
    Return Boolean interpretation of string input.
204
205
    :param input_check: String to check if it means True or False.
206
    :type input_check: str
207
    """
208
    return str(input_check).lower() in ("yes", "true", "t", "1", "y")
209
210
211
def is_amd64():
212
    """
213
    Check if script is running on an AMD64 system.
214
    """
215
    return platform.machine().endswith("64")
216
217
218
def is_windows():
219
    """
220
    Check if script is running on Windows.
221
    """
222
    return platform.system() == "Windows"
223
224
225
def get_seven_zip(talkative=False):
226
    """
227
    Return name of 7-Zip executable.
228
    On POSIX, it MUST be 7za.
229
    On Windows, it can be installed or supplied with the script.
230
    :func:`win_seven_zip` is used to determine if it's installed.
231
232
    :param talkative: Whether to output to screen. False by default.
233
    :type talkative: bool
234
    """
235
    return win_seven_zip(talkative) if is_windows() else "7za"
236
237
238
def win_seven_zip(talkative=False):
239
    """
240
    For Windows, check where 7-Zip is ("where", pretty much).
241
    Consult registry first for any installed instances of 7-Zip.
242
243
    :param talkative: Whether to output to screen. False by default.
244
    :type talkative: bool
245
    """
246
    if talkative:
247
        print("CHECKING INSTALLED FILES...")
248
    try:
249
        import winreg  # windows registry
1 ignored issue
show
Configuration introduced by
The import winreg could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
250
        hk7z = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\7-Zip")
251
        path = winreg.QueryValueEx(hk7z, "Path")
252
    except OSError as exc:
253
        if talkative:
254
            print("SOMETHING WENT WRONG")
255
            print(str(exc))
256
            print("TRYING LOCAL FILES...")
257
        return win_seven_zip_local(talkative)
258
    else:
259
        if talkative:
260
            print("7ZIP USING INSTALLED FILES")
261
        return '"{0}"'.format(os.path.join(path[0], "7z.exe"))
262
263
264
def win_seven_zip_local(talkative=False):
265
    """
266
    If 7-Zip isn't in the registry, fall back onto supplied executables.
267
    If *those* aren't there, return "error".
268
269
    :param talkative: Whether to output to screen. False by default.
270
    :type talkative: bool
271
    """
272
    listdir = os.listdir(os.getcwd())
273
    filecount = 0
274
    for i in listdir:
275
        if i in ["7za.exe", "7za64.exe"]:
276
            filecount += 1
277
    if filecount == 2:
278
        if talkative:
279
            print("7ZIP USING LOCAL FILES")
280
        if is_amd64():
281
            return "7za64.exe"
282
        else:
283
            return "7za.exe"
284
    else:
285
        if talkative:
286
            print("NO LOCAL FILES")
287
        return "error"
288
289
290
def get_core_count():
291
    """
292
    Find out how many CPU cores this system has.
293
    """
294
    try:
295
        cores = str(compat.enum_cpus())  # 3.4 and up
296
    except NotImplementedError:
297
        cores = "1"  # 3.2-3.3
298
    else:
299
        if compat.enum_cpus() is None:
300
            cores = "1"
301
    return cores
302
303
304
def prep_seven_zip(talkative=False):
305
    """
306
    Check for presence of 7-Zip.
307
    On POSIX, check for p7zip.
308
    On Windows, check for 7-Zip.
309
310
    :param talkative: Whether to output to screen. False by default.
311
    :type talkative: bool
312
    """
313
    if is_windows():
314
        return get_seven_zip(talkative) != "error"
315
    else:
316
        try:
317
            path = compat.where_which("7za")
318
        except ImportError:
319
            if talkative:
320
                print("PLEASE INSTALL SHUTILWHICH WITH PIP")
321
            return False
322
        else:
323
            if path is None:
324
                if talkative:
325
                    print("NO 7ZIP")
326
                    print("PLEASE INSTALL p7zip")
327
                return False
328
            else:
329
                if talkative:
330
                    print("7ZIP FOUND AT {0}".format(path))
331
                return True
332
333
334
def increment(version, inc=3):
335
    """
336
    Increment version by given number. For repeated lookups.
337
338
    :param version: w.x.y.ZZZZ, becomes w.x.y.(ZZZZ + increment).
339
    :type version: str
340
341
    :param inc: What to increment by. Default is 3.
342
    :type inc: str
343
    """
344
    splitos = version.split(".")
345
    splitos[3] = int(splitos[3])
346
    if splitos[3] > 9996:  # prevent overflow
347
        splitos[3] = 0
348
    splitos[3] += int(inc)
349
    splitos[3] = str(splitos[3])
350
    return ".".join(splitos)
351
352
353
def stripper(name):
354
    """
355
    Strip fluff from bar filename.
356
357
    :param name: Bar filename, must contain '-nto+armle-v7+signed.bar'.
358
    :type name: str
359
    """
360
    return name.replace("-nto+armle-v7+signed.bar", "")
361
362
363
def generate_urls(baseurl, osversion, radioversion, core=False):
364
    """
365
    Generate a list of OS URLs and a list of radio URLs based on input.
366
367
    :param baseurl: The URL, from http to the hashed software release.
368
    :type baseurl: str
369
370
    :param osversion: OS version.
371
    :type osversion: str
372
373
    :param radioversion: Radio version.
374
    :type radioversion: str
375
376
    :param core: Whether or not to return core URLs as well.
377
    :type core: bool
378
    """
379
    suffix = "nto+armle-v7+signed.bar"
380
    osurls = [
381
        "{0}/winchester.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix),
382
        "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix),
383
        "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix),
384
        "{0}/qc8974.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
385
    ]
386
    radiourls = [
387
        "{0}/m5730-{1}-{2}".format(baseurl, radioversion, suffix),
388
        "{0}/qc8960-{1}-{2}".format(baseurl, radioversion, suffix),
389
        "{0}/qc8960.omadm-{1}-{2}".format(baseurl, radioversion, suffix),
390
        "{0}/qc8960.wtr-{1}-{2}".format(baseurl, radioversion, suffix),
391
        "{0}/qc8960.wtr5-{1}-{2}".format(baseurl, radioversion, suffix),
392
        "{0}/qc8930.wtr5-{1}-{2}".format(baseurl, radioversion, suffix),
393
        "{0}/qc8974.wtr2-{1}-{2}".format(baseurl, radioversion, suffix)
394
    ]
395
    coreurls = []
396
    splitos = osversion.split(".")
397
    splitos = [int(i) for i in splitos]
398
    if splitos[1] >= 4 or (splitos[1] == 3 and splitos[2] >= 1):  # 10.3.1+
399
        osurls[2] = osurls[2].replace("qc8960.factory_sfi", "qc8960.factory_sfi_hybrid_qc8x30")
400
        osurls[3] = osurls[3].replace("qc8974.factory_sfi", "qc8960.factory_sfi_hybrid_qc8974")
401
    if core:
402
        for url in osurls:
403
            coreurls.append(url.replace(".desktop", ""))
404
    return osurls, radiourls, coreurls
405
406
407
def generate_lazy_urls(baseurl, osversion, radioversion, device):
408
    """
409
    Generate a pair of OS/radio URLs based on input.
410
411
    :param baseurl: The URL, from http to the hashed software release.
412
    :type baseurl: str
413
414
    :param osversion: OS version.
415
    :type osversion: str
416
417
    :param radioversion: Radio version.
418
    :type radioversion: str
419
420
    :param device: Device to use.
421
    :type device: int
422
    """
423
    suffix = "nto+armle-v7+signed.bar"
424
    splitos = [int(i) for i in osversion.split(".")]
425
    if device == 0:
426
        osurl = "{0}/winchester.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
427
        radiourl = "{0}/m5730-{1}-{2}".format(baseurl, radioversion, suffix)
428
    elif device == 1:
429
        osurl = "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
430
        radiourl = "{0}/qc8960-{1}-{2}".format(baseurl, radioversion, suffix)
431
    elif device == 2:
432
        osurl = "{0}/qc8960.verizon_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
433
        radiourl = "{0}/qc8960.omadm-{1}-{2}".format(baseurl, radioversion, suffix)
434
    elif device == 3:
435
        osurl = "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
436
        radiourl = "{0}/qc8960.wtr-{1}-{2}".format(baseurl, radioversion, suffix)
437
    elif device == 4:
438
        osurl = "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
439
        radiourl = "{0}/qc8960.wtr5-{1}-{2}".format(baseurl, radioversion, suffix)
440
    elif device == 5:
441
        osurl = "{0}/qc8960.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
442
        radiourl = "{0}/qc8930.wtr5-{1}-{2}".format(baseurl, radioversion, suffix)
443
        if (splitos[1] >= 4) or (splitos[1] == 3 and splitos[2] >= 1):
444
            osurl = osurl.replace("qc8960.factory_sfi", "qc8960.factory_sfi_hybrid_qc8x30")
445
    elif device == 6:
446
        osurl = "{0}/qc8974.factory_sfi.desktop-{1}-{2}".format(baseurl, osversion, suffix)
447
        radiourl = "{0}/qc8974.wtr2-{1}-{2}".format(baseurl, radioversion, suffix)
448
        if (splitos[1] >= 4) or (splitos[1] == 3 and splitos[2] >= 1):
449
            osurl = osurl.replace("qc8974.factory_sfi", "qc8960.factory_sfi_hybrid_qc8974")
450
    return osurl, radiourl
451
452
453
def bulk_urls(baseurl, osversion, radioversion, core=False, alturl=None):
454
    """
455
    Generate all URLs, plus extra Verizon URLs.
456
457
    :param baseurl: The URL, from http to the hashed software release.
458
    :type baseurl: str
459
460
    :param osversion: OS version.
461
    :type osversion: str
462
463
    :param radioversion: Radio version.
464
    :type radioversion: str
465
466
    :param device: Device to use.
467
    :type device: int
468
469
    :param core: Whether or not to return core URLs as well.
470
    :type core: bool
471
472
    :param alturl: The base URL for any alternate radios.
473
    :type alturl: str
474
    """
475
    osurls, radurls, coreurls = generate_urls(baseurl, osversion, radioversion, core)
476
    vzwos, vzwrad = generate_lazy_urls(baseurl, osversion, radioversion, 2)
477
    osurls.append(vzwos)
478
    radurls.append(vzwrad)
479
    vzwcore = vzwos.replace("sfi.desktop", "sfi")
480
    if core:
481
        coreurls.append(vzwcore)
482
    osurls = list(set(osurls))  # pop duplicates
483
    radurls = list(set(radurls))
484
    if core:
485
        coreurls = list(set(coreurls))
486
    if alturl is not None:
487
        radiourls2 = []
488
        for rad in radurls:
489
            radiourls2.append(rad.replace(baseurl, alturl))
490
        radurls = radiourls2
491
        del radiourls2
492
    return osurls, coreurls, radurls
493
494
495
def line_begin():
496
    """
497
    Go to beginning of line, to overwrite whatever's there.
498
    """
499
    sys.stdout.write("\r")
500
    sys.stdout.flush()
501
502
503
def spinner_clear():
504
    """
505
    Get rid of any spinner residue left in stdout.
506
    """
507
    sys.stdout.write("\b \b")
508
    sys.stdout.flush()
509
510
511
class Spinner(object):
512
    """
513
    A basic spinner using itertools. No need for progress.
514
    """
515
516
    def __init__(self):
517
        self.wheel = itertools.cycle(['-', '/', '|', '\\'])
518
        self.file = dummy.UselessStdout()
519
520
    def after(self):
521
        """
522
        Iterate over itertools.cycle, write to file.
523
        """
524
        try:
525
            self.file.write(next(self.wheel))
526
            self.file.flush()
527
            self.file.write("\b\r")
528
            self.file.flush()
529
        except (KeyboardInterrupt, SystemExit):  # pragma: no cover
530
            self.stop()
531
532
    def stop(self):
533
        """
534
        Kill output.
535
        """
536
        self.file = dummy.UselessStdout()
537
538
539
class SpinManager(object):
540
    """
541
    Wraps around the itertools spinner, runs it in another thread.
542
    """
543
544
    def __init__(self):
545
        spinner = Spinner()
546
        self.spinner = spinner
547
        self.thread = threading.Thread(target=self.loop, args=())
548
        self.thread.daemon = True
549
        self.scanning = False
550
        self.spinner.file = dummy.UselessStdout()
551
552
    def start(self):
553
        """
554
        Begin the spinner.
555
        """
556
        self.spinner.file = sys.stderr
557
        self.scanning = True
558
        self.thread.start()
559
560
    def loop(self):
561
        """
562
        Spin if scanning, clean up if not.
563
        """
564
        while self.scanning:
565
            time.sleep(0.5)
566
            try:
567
                line_begin()
568
                self.spinner.after()
569
            except (KeyboardInterrupt, SystemExit):  # pragma: no cover
570
                self.scanning = False
571
                self.stop()
572
573
    def stop(self):
574
        """
575
        Stop the spinner.
576
        """
577
        self.spinner.stop()
578
        self.scanning = False
579
        spinner_clear()
580
        line_begin()
581
        if not is_windows():  # pragma: no cover
582
            print("\n")
583
584
585
def return_and_delete(target):
586
    """
587
    Read text file, then delete it. Return contents.
588
589
    :param target: Text file to read.
590
    :type target: str
591
    """
592
    with open(target, "r") as thefile:
593
        content = thefile.read()
594
    os.remove(target)
595
    return content
596
597
598
def verify_loader_integrity(loaderfile):
599
    """
600
    Test for created loader integrity. Windows-only.
601
602
    :param loaderfile: Path to loader.
603
    :type loaderfile: str
604
    """
605
    if not is_windows():
606
        pass
607
    else:
608
        excode = None
609
        try:
610
            with open(os.devnull, 'rb') as dnull:
611
                cmd = "{0} fileinfo".format(loaderfile)
612
                excode = subprocess.call(cmd, stdout=dnull, stderr=subprocess.STDOUT)
613
        except OSError:
614
            excode = -1
615
        return excode == 0  # 0 if OK, non-zero if something broke
616
617
618
def verify_bulk_loaders(a_dir):
619
    """
620
    Run :func:`verify_loader_integrity` for all files in a dir.
621
622
    :param a_dir: Directory to use.
623
    :type a_dir: str
624
    """
625
    if not is_windows():
626
        pass
627
    else:
628
        files = [file for file in os.listdir(a_dir) if not os.path.isdir(file)]
629
        brokens = []
630
        for file in files:
631
            if file.endswith(".exe") and file.startswith(bbconstants.PREFIXES):
632
                print("TESTING: {0}".format((file)))
633
                if not verify_loader_integrity(file):
634
                    brokens.append(file)
635
        return brokens
636
637
638
def workers(input_data):
639
    """
640
    Count number of CPU workers, smaller of number of threads and length of data.
641
642
    :param input_data: Input data, some iterable.
643
    :type input_data: list
644
    """
645
    runners = len(input_data) if len(input_data) < compat.enum_cpus() else compat.enum_cpus()
646
    return runners
647
648
649
def prep_logfile():
650
    """
651
    Prepare log file, labeling it with current date. Select folder based on frozen status.
652
    """
653
    logfile = "{0}.txt".format(time.strftime("%Y_%m_%d_%H%M%S"))
654
    root = os.getcwd() if getattr(sys, 'frozen', False) else os.path.expanduser("~")
655
    basefolder = os.path.join(root, "lookuplogs")
656
    os.makedirs(basefolder, exist_ok=True)
657
    record = os.path.join(basefolder, logfile)
658
    open(record, "w").close()
659
    return record
660
661
662
def prepends(file, pre, suf):
663
    """
664
    Check if filename starts with/ends with stuff.
665
666
    :param file: File to check.
667
    :type file: str
668 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
669
    :param pre: Prefix(es) to check.
670
    :type pre: str or list or tuple
671
672
    :param suf: Suffix(es) to check.
673
    :type suf: str or list or tuple
674
    """
675
    return file.startswith(pre) and file.endswith(suf)
676
677
678
def cappath_config_loader(homepath=None):
679
    """
680
    Read a ConfigParser file to get cap preferences.
681
682
    :param homepath: Folder containing ini file. Default is user directory.
683
    :type homepath: str
684
    """
685
    config = configparser.ConfigParser()
686
    homepath = os.path.expanduser("~") if homepath is None else homepath
687
    conffile = os.path.join(homepath, "bbarchivist.ini")
688
    if not os.path.exists(conffile):
689
        open(conffile, 'w').close()
690
    config.read(conffile)
691
    if not config.has_section('cap'):
692
        config['cap'] = {}
693
    capini = config['cap']
694
    cappath = capini.get('path', fallback=bbconstants.CAP.location)
695
    return cappath
696
697
698
def cappath_config_writer(cappath=None, homepath=None):
699
    """
700
    Write a ConfigParser file to store cap preferences.
701
702
    :param cappath: Method to use.
703
    :type cappath: str
704
705
    :param homepath: Folder containing ini file. Default is user directory.
706
    :type homepath: str
707
    """
708
    if cappath is None:
709
        cappath = grab_cap()
710
    config = configparser.ConfigParser()
711
    if homepath is None:
712
        homepath = os.path.expanduser("~")
713
    conffile = os.path.join(homepath, "bbarchivist.ini")
714
    if not os.path.exists(conffile):
715
        open(conffile, 'w').close()
716
    config.read(conffile)
717
    if not config.has_section('cap'):
718
        config['cap'] = {}
719
    config['cap']['path'] = cappath
720
    with open(conffile, "w") as configfile:
721
        config.write(configfile)
722