bbarchivist.archiveutils   F
last analyzed

Complexity

Total Complexity 78

Size/Duplication

Total Lines 626
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 0
Metric Value
eloc 199
dl 0
loc 626
rs 2.16
c 0
b 0
f 0
ccs 191
cts 191
cp 1
wmc 78

36 Functions

Rating   Name   Duplication   Size   Complexity  
A pack_tclloader() 0 16 2
A tar_verify() 0 8 1
A filter_method_nosz() 0 17 4
A tgz_compress() 0 15 1
A pack_tclloader_zip() 0 19 4
A filter_method() 0 14 3
A smart_is_tarfile() 0 13 3
A zip_verify() 0 13 3
A generic_tarfile_verify() 0 17 4
A tarzip_verifier() 0 13 3
A compress_config_loader() 0 12 3
A txz_compress() 0 15 2
A zip_compress() 0 14 2
A tar_compress() 0 12 1
A generic_nocompresslevel() 0 15 2
A filtercomp() 0 19 2
A prep_compress_function() 0 21 4
A tgz_verify() 0 8 1
A decide_verifier_printer() 0 14 2
A decide_verifier() 0 17 3
A generic_compresslevel() 0 18 2
A compress_config_writer() 0 13 2
A compressfilter_select() 0 24 4
A txz_verify() 0 12 2
A compress() 0 29 2
A tbz_compress() 0 15 1
A generic_tarfile_compress() 0 21 2
A calculate_strength() 0 6 2
A verify() 0 20 2
A extract_zip() 0 12 1
A tbz_verify() 0 8 1
A filter_without_boolfilt() 0 14 1
A filter_with_boolfilt() 0 14 1
A verify_android_tools() 0 10 2
A compressfilter() 0 15 1
A extract_android_tools() 0 13 2

How to fix   Complexity   

Complexity

Complex classes like bbarchivist.archiveutils often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python3
2 5
"""This module is used to operate with archives."""
3
4 5
import os  # filesystem read
5 5
import tarfile  # txz/tbz/tgz/tar compression
6 5
import zipfile  # zip compresssion
7
8 5
from bbarchivist import barutils  # zip tester
9 5
from bbarchivist import bbconstants  # premade stuff
10 5
from bbarchivist import decorators  # timer
11 5
from bbarchivist import iniconfig  # config parsing
12 5
from bbarchivist import sevenziputils  # 7z
13 5
from bbarchivist import utilities  # platform determination
14
15 5
__author__ = "Thurask"
16 5
__license__ = "WTFPL v2"
17 5
__copyright__ = "2015-2019 Thurask"
18
19
20 5
def smart_is_tarfile(filepath):
21
    """
22
    :func:`tarfile.is_tarfile` plus error handling.
23
24
    :param filepath: Filename.
25
    :type filepath: str
26
    """
27 5
    try:
28 5
        istar = tarfile.is_tarfile(filepath)
29 5
    except (OSError, IOError):
30 5
        return False
31
    else:
32 5
        return istar
33
34
35 5
def generic_tarfile_verify(filepath, method):
36
    """
37
    Verify that a tar/tgz/tbz/txz file is valid and working.
38
39
    :param filepath: Filename.
40
    :type filepath: str
41
42
    :param method: Tarfile read method.
43
    :type method: str
44
    """
45 5
    if smart_is_tarfile(filepath):
46 5
        with tarfile.open(filepath, method) as thefile:
47 5
            mems = thefile.getmembers()
48 5
        sentinel = False if not mems else True
49
    else:
50 5
        sentinel = False
51 5
    return sentinel
52
53
54 5
def generic_tarfile_compress(archivename, filename, method, strength=5):
55
    """
56
    Pack a file into an uncompressed/gzip/bzip2/LZMA tarfile.
57
58
    :param archivename: Archive name.
59
    :type archivename: str
60
61
    :param filename: Name of file to pack into archive.
62
    :type filename: str
63
64
    :param method: Tarfile compress method.
65
    :type method: str
66
67
    :param strength: Compression strength. 5 is normal, 9 is ultra.
68
    :type strength: int
69
    """
70 5
    nocomp = ["w:", "w:xz"]  # methods w/o compression: tar, tar.xz
71 5
    if method in nocomp:
72 5
        generic_nocompresslevel(archivename, filename, method)
73
    else:
74 5
        generic_compresslevel(archivename, filename, method, strength)
75
76
77 5
def generic_compresslevel(archivename, filename, method, strength=5):
78
    """
79
    Pack a file into a gzip/bzip2 tarfile.
80
81
    :param archivename: Archive name.
82
    :type archivename: str
83
84
    :param filename: Name of file to pack into archive.
85
    :type filename: str
86
87
    :param method: Tarfile compress method.
88
    :type method: str
89
90
    :param strength: Compression strength. 5 is normal, 9 is ultra.
91
    :type strength: int
92
    """
93 5
    with tarfile.open(archivename, method, compresslevel=strength) as afile:
94 5
        afile.add(filename, filter=None, arcname=os.path.basename(filename))
95
96
97 5
def generic_nocompresslevel(archivename, filename, method):
98
    """
99
    Pack a file into an uncompressed/LZMA tarfile.
100
101
    :param archivename: Archive name.
102
    :type archivename: str
103
104
    :param filename: Name of file to pack into archive.
105
    :type filename: str
106
107
    :param method: Tarfile compress method.
108
    :type method: str
109
    """
110 5
    with tarfile.open(archivename, method) as afile:
111 5
        afile.add(filename, filter=None, arcname=os.path.basename(filename))
112
113
114 5
@decorators.timer
115
def tar_compress(filepath, filename):
116
    """
117
    Pack a file into an uncompressed tarfile.
118
119
    :param filepath: Basename of file, no extension.
120
    :type filepath: str
121
122
    :param filename: Name of file to pack.
123
    :type filename: str
124
    """
125 5
    generic_tarfile_compress("{0}.tar".format(filepath), filename, "w:")
126
127
128 5
def tar_verify(filepath):
129
    """
130
    Verify that a tar file is valid and working.
131
132
    :param filepath: Filename.
133
    :type filepath: str
134
    """
135 5
    return generic_tarfile_verify(filepath, "r:")
136
137
138 5
@decorators.timer
139 5
def tgz_compress(filepath, filename, strength=5):
140
    """
141
    Pack a file into a gzip tarfile.
142
143
    :param filepath: Basename of file, no extension.
144
    :type filepath: str
145
146
    :param filename: Name of file to pack.
147
    :type filename: str
148
149
    :param strength: Compression strength. 5 is normal, 9 is ultra.
150
    :type strength: int
151
    """
152 5
    generic_tarfile_compress("{0}.tar.gz".format(filepath), filename, "w:gz", strength)
153
154
155 5
def tgz_verify(filepath):
156
    """
157
    Verify that a tar.gz file is valid and working.
158
159
    :param filepath: Filename.
160
    :type filepath: str
161
    """
162 5
    return generic_tarfile_verify(filepath, "r:gz")
163
164
165 5
@decorators.timer
166 5
def tbz_compress(filepath, filename, strength=5):
167
    """
168
    Pack a file into a bzip2 tarfile.
169
170
    :param filepath: Basename of file, no extension.
171
    :type filepath: str
172
173
    :param filename: Name of file to pack.
174
    :type filename: str
175
176
    :param strength: Compression strength. 5 is normal, 9 is ultra.
177
    :type strength: int
178
    """
179 5
    generic_tarfile_compress("{0}.tar.bz2".format(filepath), filename, "w:bz2", strength)
180
181
182 5
def tbz_verify(filepath):
183
    """
184
    Verify that a tar.bz2 file is valid and working.
185
186
    :param filepath: Filename.
187
    :type filepath: str
188
    """
189 5
    return generic_tarfile_verify(filepath, "r:bz2")
190
191
192 5
@decorators.timer
193
def txz_compress(filepath, filename):
194
    """
195
    Pack a file into a LZMA tarfile.
196
197
    :param filepath: Basename of file, no extension.
198
    :type filepath: str
199
200
    :param filename: Name of file to pack.
201
    :type filename: str
202
    """
203 5
    if not utilities.new_enough(3, 3):
204 5
        pass
205
    else:
206 4
        generic_tarfile_compress("{0}.tar.xz".format(filepath), filename, "w:xz")
207
208
209 5
def txz_verify(filepath):
210
    """
211
    Verify that a tar.xz file is valid and working.
212
213
    :param filepath: Filename.
214
    :type filepath: str
215
    """
216 5
    if not utilities.new_enough(3, 3):
217 5
        sentinel = None
218
    else:
219 4
        sentinel = generic_tarfile_verify(filepath, "r:xz")
220 5
    return sentinel
221
222
223 5
@decorators.timer
224
def zip_compress(filepath, filename):
225
    """
226
    Pack a file into a DEFLATE zipfile.
227
228
    :param filepath: Basename of file, no extension.
229
    :type filepath: str
230
231
    :param filename: Name of file to pack.
232
    :type filename: str
233
    """
234 5
    zipf = "{0}.zip".format(filepath)
235 5
    with zipfile.ZipFile(zipf, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) as zfile:
236 5
        zfile.write(filename, arcname=os.path.basename(filename))
237
238
239 5
def zip_verify(filepath):
240
    """
241
    Verify that a .zip file is valid and working.
242
243
    :param filepath: Filename.
244
    :type filepath: str
245
    """
246 5
    if zipfile.is_zipfile(filepath):
247 5
        brokens = barutils.bar_tester(filepath)
248 5
        sentinel = True if brokens != filepath else False
249
    else:
250 5
        sentinel = False
251 5
    return sentinel
252
253
254 5
def filter_method(method, szexe=None):
255
    """
256
    Make sure methods are OK.
257
258
    :param method: Compression method to use.
259
    :type method: str
260
261
    :param szexe: Path to 7z executable, if needed.
262
    :type szexe: str
263
    """
264 5
    if not utilities.new_enough(3, 3) and method == "txz":
265 5
        method = "zip"  # fallback
266 5
    method = filter_method_nosz(method, szexe)
267 5
    return method
268
269
270 5
def filter_method_nosz(method, szexe=None):
271
    """
272
    Make sure 7-Zip is OK.
273
274
    :param method: Compression method to use.
275
    :type method: str
276
277
    :param szexe: Path to 7z executable, if needed.
278
    :type szexe: str
279
    """
280 5
    if method == "7z" and szexe is None:
281 5
        ifexists = utilities.prep_seven_zip()  # see if 7z exists
282 5
        if not ifexists:
283 5
            method = "zip"  # fallback
284
        else:
285 5
            szexe = utilities.get_seven_zip(False)
286 5
    return method
287
288
289 5
def calculate_strength():
290
    """
291
    Determine zip/gzip/bzip2 strength by OS bit setting.
292
    """
293 5
    strength = 9 if utilities.is_amd64() else 5
294 5
    return strength
295
296
297 5
def filter_with_boolfilt(files, criterion, critargs):
298
    """
299
    Return everything that matches criterion.
300
301
    :param files: Files to work on.
302
    :type files: list(str)
303
304
    :param criterion: Function to use for evaluation.
305
    :type criterion: func
306
307
    :param critargs: Arguments for function, other than file.
308
    :type critargs: list
309
    """
310 5
    return [file for file in files if criterion(file, *critargs)]
311
312
313 5
def filter_without_boolfilt(files, criterion, critargs):
314
    """
315
    Return everything that doesn't match criterion.
316
317
    :param files: Files to work on.
318
    :type files: list(str)
319
320
    :param criterion: Function to use for evaluation.
321
    :type criterion: func
322
323
    :param critargs: Arguments for function, other than file.
324
    :type critargs: list
325
    """
326 5
    return [file for file in files if not criterion(file, *critargs)]
327
328
329 5
def filtercomp(files, criterion, critargs, boolfilt=True):
330
    """
331
    :param files: Files to work on.
332
    :type files: list(str)
333
334
    :param criterion: Function to use for evaluation.
335
    :type criterion: func
336
337
    :param critargs: Arguments for function, other than file.
338
    :type critargs: list
339
340
    :param boolfilt: True if comparing criterion, False if comparing not criterion.
341
    :type boolfilt: bool
342
    """
343 5
    if boolfilt:
344 5
        fx2 = filter_with_boolfilt(files, criterion, critargs)
345
    else:
346 5
        fx2 = filter_without_boolfilt(files, criterion, critargs)
347 5
    return fx2
348
349
350 5
def compressfilter_select(filepath, files, selective=False):
351
    """
352
    :param filepath: Working directory. Required.
353
    :type filepath: str
354
355
    :param files: List of files in filepath.
356
    :type files: list(str)
357
358
    :param selective: Only compress autoloaders. Default is false.
359
    :type selective: bool/str
360
    """
361 5
    arx = bbconstants.ARCS
362 5
    pfx = bbconstants.PREFIXES
363 5
    if selective is None:
364 5
        filt2 = os.listdir(filepath)
365 5
    elif selective == "arcsonly":
366 5
        filt2 = filtercomp(files, utilities.prepends, ("", arx))
367 5
    elif selective:
368 5
        filt0 = filtercomp(files, utilities.prepends, (pfx, ""))
369 5
        filt1 = filtercomp(filt0, utilities.prepends, ("", arx), False)  # pop archives
370 5
        filt2 = filtercomp(filt1, utilities.prepends, ("", ".exe"))  # include exes
371
    else:
372 5
        filt2 = filtercomp(files, utilities.prepends, ("", arx), False)  # pop archives
373 5
    return filt2
374
375
376 5
def compressfilter(filepath, selective=False):
377
    """
378
    Filter directory listing of working directory.
379
380
    :param filepath: Working directory. Required.
381
    :type filepath: str
382
383
    :param selective: Only compress autoloaders. Default is false.
384
    :type selective: bool/str
385
    """
386
387 5
    files = [file for file in os.listdir(filepath) if not os.path.isdir(file)]
388 5
    filt2 = compressfilter_select(filepath, files, selective)
389 5
    filt3 = [os.path.join(filepath, file) for file in filt2]
390 5
    return filt3
391
392
393 5
def prep_compress_function(method="7z", szexe=None, errors=False):
394
    """
395
    Prepare compression function and partial arguments.
396
397
    :param method: Compression type. Default is "7z".
398
    :type method: str
399
400
    :param szexe: Path to 7z executable, if needed.
401
    :type szexe: str
402
403
    :param errors: Print completion status message. Default is false.
404
    :type errors: bool
405
    """
406 5
    methods = {"7z": sevenziputils.sz_compress, "tgz": tgz_compress, "txz": txz_compress,
407
               "tbz": tbz_compress, "tar": tar_compress, "zip": zip_compress}
408 5
    args = [szexe] if method == "7z" else []
409 5
    if method in ("7z", "tbz", "tgz"):
410 5
        args.append(calculate_strength())
411 5
    if method == "7z":
412 5
        args.append(errors)
413 5
    return methods[method], args
414
415
416 5
def compress(filepath, method="7z", szexe=None, selective=False, errors=False):
417
    """
418
    Compress all autoloader files in a given folder, with a given method.
419
420
    :param filepath: Working directory. Required.
421
    :type filepath: str
422
423
    :param method: Compression type. Default is "7z".
424
    :type method: str
425
426
    :param szexe: Path to 7z executable, if needed.
427
    :type szexe: str
428
429
    :param selective: Only compress autoloaders. Default is false.
430
    :type selective: bool
431
432
    :param errors: Print completion status message. Default is false.
433
    :type errors: bool
434
    """
435 5
    method = filter_method(method, szexe)
436 5
    files = compressfilter(filepath, selective)
437 5
    for file in files:
438 5
        fname = os.path.basename(file)
439 5
        filename = os.path.splitext(fname)[0]
440 5
        fileloc = os.path.join(filepath, filename)
441 5
        print("COMPRESSING: {0}".format(fname))
442 5
        compfunc, extargs = prep_compress_function(method, szexe, errors)
443 5
        compfunc(fileloc, file, *extargs)
444 5
    return True
445
446
447 5
def tarzip_verifier(file):
448
    """
449
    Assign .tar.xxx, .tar and .zip verifiers.
450
451
    :param file: Filename.
452
    :type file: str
453
    """
454 5
    maps = {".tar.gz": tgz_verify, ".tar.xz": txz_verify,
455
            ".tar.bz2": tbz_verify, ".tar": tar_verify,
456
            ".zip": zip_verify, ".bar": zip_verify}
457 5
    for key, value in maps.items():
458 5
        if file.endswith(key):
459 5
            return value(file)
460
461
462 5
def decide_verifier(file, szexe=None):
463
    """
464
    Decide which verifier function to use.
465
466
    :param file: Filename.
467
    :type file: str
468
469
    :param szexe: Path to 7z executable, if needed.
470
    :type szexe: str
471
    """
472 5
    print("VERIFYING: {0}".format(file))
473 5
    if file.endswith(".7z") and szexe is not None:
474 5
        verif = sevenziputils.sz_verify(os.path.abspath(file), szexe)
475
    else:
476 5
        verif = tarzip_verifier(file)
477 5
    decide_verifier_printer(file, verif)
478 5
    return verif
479
480
481 5
def decide_verifier_printer(file, verif):
482
    """
483
    Print status of verifier function.
484
485
    :param file: Filename.
486
    :type file: str
487
488
    :param verif: If the file is OK or not.
489
    :type verif: bool
490
    """
491 5
    if not verif:
492 5
        print("{0} IS BROKEN!".format(os.path.basename(file)))
493
    else:
494 5
        print("{0} OK".format(os.path.basename(file)))
495
496
497 5
def verify(filepath, method="7z", szexe=None, selective=False):
498
    """
499
    Verify specific archive files in a given folder.
500
501
    :param filepath: Working directory. Required.
502
    :type filepath: str
503
504
    :param method: Compression type. Default is "7z". Defined in source.
505
    :type method: str
506
507
    :param szexe: Path to 7z executable, if needed.
508
    :type szexe: str
509
510
    :param selective: Only verify autoloaders. Default is false.
511
    :type selective: bool
512
    """
513 5
    method = filter_method(method, szexe)
514 5
    files = compressfilter(filepath, selective)
515 5
    for file in files:
516 5
        decide_verifier(file, szexe)
517
518
519 5
def compress_config_loader(homepath=None):
520
    """
521
    Read a ConfigParser file to get compression preferences.
522
523
    :param homepath: Folder containing ini file. Default is user directory.
524
    :type homepath: str
525
    """
526 5
    compini = iniconfig.generic_loader('compression', homepath)
527 5
    method = compini.get('method', fallback="7z")
528 5
    if not utilities.new_enough(3, 3) and method == "txz":
529 5
        method = "zip"  # for 3.2 compatibility
530 5
    return method
531
532
533 5
def compress_config_writer(method=None, homepath=None):
534
    """
535
    Write a ConfigParser file to store compression preferences.
536
537
    :param method: Method to use.
538
    :type method: str
539
540
    :param homepath: Folder containing ini file. Default is user directory.
541
    :type homepath: str
542
    """
543 5
    method = compress_config_loader() if method is None else method
544 5
    results = {"method": method}
545 5
    iniconfig.generic_writer("compression", results, homepath)
546
547
548 5
def verify_android_tools(tooldir):
549
    """
550
    Verify Android SDK platform tools archives.
551
552
    :param tooldir: Directory containing platform tools zips.
553
    :type tooldir: str
554
    """
555 5
    zipver = [zip_verify(os.path.join(os.path.abspath(tooldir), x)) for x in os.listdir(os.path.abspath(tooldir)) if x.endswith(".zip")]
556 5
    sentinel = True if zipver == [True, True, True] else False  # all OK
557 5
    return sentinel
558
559
560 5
def extract_zip(infile, outpath):
561
    """
562
    Extract a zip file to a given output path.
563
564
    :param infile: Path to input zip file.
565
    :type infile: str
566
567
    :param outpath: Path to extract input zip to.
568
    :type outpath: str
569
    """
570 5
    zipf = zipfile.ZipFile(infile)
571 5
    zipf.extractall(outpath)
572
573
574 5
def extract_android_tools(tooldir):
575
    """
576
    Extract Android SDK platform tools archives.
577
578
    :param tooldir: Directory containing platform tools zips.
579
    :type tooldir: str
580
    """
581 5
    plats = [x for x in os.listdir(os.path.abspath(tooldir)) if x.endswith(".zip")]
582 5
    for zipx in plats:
583 5
        platform = os.path.basename(zipx).replace("platform-tools-latest-", "").replace(".zip", "")
584 5
        platdir = os.path.join(os.path.abspath(tooldir), platform)
585 5
        os.makedirs(platdir)
586 5
        extract_zip(os.path.join(os.path.abspath(tooldir), zipx), platdir)
587
588
589 5
@decorators.timer
590
def pack_tclloader(dirname, filename):
591
    """
592
    Compress Android autoloader folder.
593
594
    :param dirname: Target folder.
595
    :type dirname: str
596
597
    :param filename: File title, without extension.
598
    :type filename: str
599
    """
600 5
    if utilities.prep_seven_zip():
601 5
        strength = calculate_strength()
602 5
        sevenziputils.pack_tclloader_sz(dirname, filename, strength)
603
    else:
604 5
        pack_tclloader_zip(dirname, filename)
605
606
607 5
def pack_tclloader_zip(dirname, filename):
608
    """
609
    Compress Android autoloader folder into a zip file.
610
611
    :param dirname: Target folder.
612
    :type dirname: str
613
614
    :param filename: File title, without extension.
615
    :type filename: str
616
    """
617 5
    zipf = "{0}.zip".format(filename)
618 5
    with zipfile.ZipFile(zipf, 'w', zipfile.ZIP_DEFLATED, allowZip64=True) as zfile:
619 5
        for root, dirs, files in os.walk(dirname):
620 5
            del dirs
621 5
            for file in files:
622 5
                print("ZIPPING: {0}".format(utilities.stripper(file)))
623 5
                abs_filename = os.path.join(root, file)
624 5
                abs_arcname = abs_filename.replace("{0}{1}".format(dirname, os.sep), "")
625
                zfile.write(abs_filename, abs_arcname)
626