Completed
Push — master ( ef95c0...e1ca39 )
by John
06:17
created

pack_tclloader()   A

Complexity

Conditions 2

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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