OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. |
| 4 |
| 5 """Command line tool for creating and extracting ar files.""" |
| 6 |
| 7 from __future__ import print_function |
| 8 |
| 9 import argparse |
| 10 import io |
| 11 import os |
| 12 import shutil |
| 13 import stat |
| 14 import sys |
| 15 import time |
| 16 |
| 17 # pylint: disable=relative-import |
| 18 import arfile |
| 19 |
| 20 |
| 21 class ProgressReporter(object): |
| 22 def __init__(self, every): |
| 23 self.every = int(every) |
| 24 self.start = time.time() |
| 25 self.filecount = 0 |
| 26 self.lastreport = 0 |
| 27 |
| 28 def inc(self): |
| 29 self.filecount += 1 |
| 30 if (self.filecount - self.lastreport) >= self.every: |
| 31 self.report() |
| 32 |
| 33 def report(self): |
| 34 if self.every: |
| 35 t = time.time()-self.start |
| 36 print(u'Took %f for %i files == %f files/second' % ( |
| 37 t, self.filecount, self.filecount/t), file=sys.stderr) |
| 38 self.lastreport = self.filecount |
| 39 |
| 40 def __del__(self): |
| 41 self.report() |
| 42 |
| 43 |
| 44 def create_cmd( |
| 45 filename, dirs, progress, read_ahead, verbose, dont_use_defaults): |
| 46 afw = arfile.ArFileWriter(filename) |
| 47 try: |
| 48 for path in dirs: |
| 49 for dirpath, child_dirs, filenames in os.walk(path): |
| 50 # In-place sort the child_dirs so we walk in lexicographical order |
| 51 child_dirs.sort() |
| 52 filenames.sort() |
| 53 for fn in filenames: |
| 54 fp = os.path.join(dirpath, fn) |
| 55 |
| 56 if verbose: |
| 57 print(fp, file=sys.stderr) |
| 58 |
| 59 progress.inc() |
| 60 |
| 61 with open(fp, 'rb') as f: |
| 62 if dont_use_defaults: |
| 63 afw.addfile( |
| 64 arfile.ArInfo.frompath(fp[len(path)+1:], cwd=path), |
| 65 f) |
| 66 continue |
| 67 |
| 68 # If a file is small, it is cheaper to just read the file rather |
| 69 # than doing a stat |
| 70 data = f.read(read_ahead) |
| 71 if len(data) < read_ahead: |
| 72 afw.addfile(arfile.ArInfo.fromdefault( |
| 73 fp[len(path)+1:], len(data)), io.BytesIO(data)) |
| 74 else: |
| 75 size = os.stat(fp).st_size |
| 76 f.seek(0) |
| 77 afw.addfile(arfile.ArInfo.fromdefault( |
| 78 fp[len(path)+1:], size), f) |
| 79 finally: |
| 80 afw.close() |
| 81 |
| 82 |
| 83 def list_cmd(filename, progress): |
| 84 afr = arfile.ArFileReader(filename, fullparse=False) |
| 85 for ai, _ in afr: |
| 86 print(ai.name) |
| 87 progress.inc() |
| 88 |
| 89 |
| 90 def extract_cmd( |
| 91 filename, progress, verbose, dont_use_defaults, blocksize=1024*64): |
| 92 afr = arfile.ArFileReader(filename, fullparse=dont_use_defaults) |
| 93 for ai, ifd in afr: |
| 94 assert not ai.name.startswith('/') |
| 95 if verbose: |
| 96 print(ai.name, file=sys.stderr) |
| 97 |
| 98 try: |
| 99 os.makedirs(os.path.dirname(ai.name)) |
| 100 except OSError: |
| 101 pass |
| 102 |
| 103 with open(ai.name, 'wb') as ofd: |
| 104 written = 0 |
| 105 while written < ai.size: |
| 106 readsize = min(blocksize, ai.size-written) |
| 107 ofd.write(ifd.read(readsize)) |
| 108 written += readsize |
| 109 |
| 110 progress.inc() |
| 111 |
| 112 |
| 113 def main(name, args): |
| 114 parser = argparse.ArgumentParser( |
| 115 prog=name, |
| 116 description=sys.modules[__name__].__doc__) |
| 117 subparsers = parser.add_subparsers( |
| 118 dest='mode', help='sub-command help') |
| 119 |
| 120 # Create command |
| 121 parser_create = subparsers.add_parser( |
| 122 'create', help='Create a new ar file') |
| 123 parser_create.add_argument( |
| 124 '-r', '--read-ahead', |
| 125 type=int, default=1024*64, |
| 126 help='Amount of data to read-ahead before doing a stat.') |
| 127 parser_create.add_argument( |
| 128 '-f', '--filename', |
| 129 type=argparse.FileType('wb'), default=sys.stdout, |
| 130 help='ar file to use') |
| 131 parser_create.add_argument( |
| 132 'dirs', nargs='+', help='Directory or file to add to the ar file') |
| 133 |
| 134 # List command |
| 135 parser_list = subparsers.add_parser('list', help='List a new ar file') |
| 136 |
| 137 # Extract command |
| 138 parser_extract = subparsers.add_parser( |
| 139 'extract', help='Extract an existing ar file to current directory') |
| 140 |
| 141 # Add to output commands |
| 142 for p in parser_list, parser_extract: |
| 143 p.add_argument( |
| 144 '-f', '--filename', |
| 145 type=argparse.FileType('rb'), default=sys.stdin, |
| 146 help='ar file to use') |
| 147 |
| 148 for p in parser_create, parser_extract: |
| 149 p.add_argument( |
| 150 '--dont-use-defaults', |
| 151 action='store_true', default=False, |
| 152 help='Don\'t use default value for file information.') |
| 153 |
| 154 p.add_argument( |
| 155 '-v', '--verbose', |
| 156 action='store_true', |
| 157 help='Output file names to stderr while running.') |
| 158 |
| 159 # Add to all commands |
| 160 for p in parser_create, parser_list, parser_extract: |
| 161 p.add_argument( |
| 162 '-p', '--progress', |
| 163 type=ProgressReporter, default='10000', |
| 164 help='Output progress information every N files.') |
| 165 |
| 166 args = parser.parse_args(args) |
| 167 mode = getattr(sys.modules[__name__], args.mode + '_cmd') |
| 168 del args.mode |
| 169 return mode(**args.__dict__) |
| 170 |
| 171 |
| 172 if __name__ == '__main__': |
| 173 sys.exit(main('artool', (a.decode('utf-8') for a in sys.argv[1:]))) |
OLD | NEW |