Mercurial > hgrepos > Python > apps > py-cutils
comparison cutils/treesum.py @ 179:53614a724bf0
Also write a (standard) CRC-32 checksum for each block of output
| author | Franz Glasner <fzglas.hg@dom66.de> |
|---|---|
| date | Sat, 11 Jan 2025 22:26:00 +0100 |
| parents | dac26a2d9de5 |
| children | 2784fdcc99e5 |
comparison
equal
deleted
inserted
replaced
| 178:dac26a2d9de5 | 179:53614a724bf0 |
|---|---|
| 20 import logging | 20 import logging |
| 21 import os | 21 import os |
| 22 import stat | 22 import stat |
| 23 import sys | 23 import sys |
| 24 import time | 24 import time |
| 25 import zlib | |
| 25 | 26 |
| 26 from . import (__version__, __revision__) | 27 from . import (__version__, __revision__) |
| 27 from . import util | 28 from . import util |
| 28 from .util import cm | 29 from .util import cm |
| 29 from .util import digest | 30 from .util import digest |
| 276 else: | 277 else: |
| 277 if opts.append_output: | 278 if opts.append_output: |
| 278 out_cm = open(opts.output, "ab") | 279 out_cm = open(opts.output, "ab") |
| 279 else: | 280 else: |
| 280 out_cm = open(opts.output, "wb") | 281 out_cm = open(opts.output, "wb") |
| 282 out_cm = CRC32Output(out_cm) | |
| 281 | 283 |
| 282 with out_cm as outfp: | 284 with out_cm as outfp: |
| 283 for d in opts.directories: | 285 for d in opts.directories: |
| 284 | 286 |
| 285 V1DirectoryTreesumGenerator( | 287 V1DirectoryTreesumGenerator( |
| 319 | 321 |
| 320 :param outfp: a *binary* file with a "write()" and a "flush()" method | 322 :param outfp: a *binary* file with a "write()" and a "flush()" method |
| 321 | 323 |
| 322 """ | 324 """ |
| 323 self._outfp = outfp | 325 self._outfp = outfp |
| 326 self._outfp.resetdigest() | |
| 324 self._outfp.write(format_bsd_line("VERSION", "1", None, False)) | 327 self._outfp.write(format_bsd_line("VERSION", "1", None, False)) |
| 325 self._outfp.flush() | 328 self._outfp.flush() |
| 326 | 329 |
| 327 # | 330 # |
| 328 # Note: Given non-default flags that are relevant for | 331 # Note: Given non-default flags that are relevant for |
| 392 self._algorithm[1], | 395 self._algorithm[1], |
| 393 dir_dgst.digest(), | 396 dir_dgst.digest(), |
| 394 "./@", | 397 "./@", |
| 395 self._use_base64)) | 398 self._use_base64)) |
| 396 self._outfp.flush() | 399 self._outfp.flush() |
| 400 self._outfp.write(format_bsd_line( | |
| 401 "CRC32", self._outfp.hexcrcdigest(), None, False)) | |
| 397 return | 402 return |
| 398 | 403 |
| 399 self._generate(os.path.normpath(root), tuple()) | 404 self._generate(os.path.normpath(root), tuple()) |
| 405 self._outfp.write(format_bsd_line( | |
| 406 "CRC32", self._outfp.hexcrcdigest(), None, False)) | |
| 400 | 407 |
| 401 def _generate(self, root, top): | 408 def _generate(self, root, top): |
| 402 logging.debug("Handling %s/%r", root, top) | 409 logging.debug("Handling %s/%r", root, top) |
| 403 path = os.path.join(root, *top) if top else root | 410 path = os.path.join(root, *top) if top else root |
| 404 with walk.ScanDir(path) as dirscan: | 411 with walk.ScanDir(path) as dirscan: |
| 520 self._use_base64)) | 527 self._use_base64)) |
| 521 self._outfp.flush() | 528 self._outfp.flush() |
| 522 return (dir_dgst.digest(), dir_size) | 529 return (dir_dgst.digest(), dir_size) |
| 523 | 530 |
| 524 | 531 |
| 532 class CRC32Output(object): | |
| 533 | |
| 534 """Wrapper for a minimal binary file contextmanager that calculates | |
| 535 the CRC32 of the written bytes on the fly. | |
| 536 | |
| 537 Also acts as context manager proxy for the given context manager. | |
| 538 | |
| 539 """ | |
| 540 | |
| 541 __slots__ = ("_fp_cm", "_fp", "_crc32") | |
| 542 | |
| 543 def __init__(self, fp_cm): | |
| 544 self._fp_cm = fp_cm | |
| 545 self._fp = None | |
| 546 self.resetdigest() | |
| 547 | |
| 548 def __enter__(self): | |
| 549 assert self._fp is None | |
| 550 self._fp = self._fp_cm.__enter__() | |
| 551 return self | |
| 552 | |
| 553 def __exit__(self, *args): | |
| 554 rv = self._fp_cm.__exit__(*args) | |
| 555 self._fp = None | |
| 556 return rv | |
| 557 | |
| 558 def write(self, what): | |
| 559 self._fp.write(what) | |
| 560 self._crc32 = zlib.crc32(what, self._crc32) | |
| 561 | |
| 562 def flush(self): | |
| 563 self._fp.flush() | |
| 564 | |
| 565 def resetdigest(self): | |
| 566 """Reset the current CRC digest""" | |
| 567 self._crc32 = zlib.crc32(b"") | |
| 568 | |
| 569 def hexcrcdigest(self): | |
| 570 """ | |
| 571 | |
| 572 :rtype: str | |
| 573 | |
| 574 """ | |
| 575 return (hex(self._crc32)[2:]).upper() | |
| 576 | |
| 577 def crcdigest(self): | |
| 578 """ | |
| 579 | |
| 580 :rtype: int | |
| 581 | |
| 582 """ | |
| 583 return self._crc32 | |
| 584 | |
| 585 | |
| 525 def normalized_compatible_mode_str(mode): | 586 def normalized_compatible_mode_str(mode): |
| 526 # XXX FIXME: Windows and "executable" | 587 # XXX FIXME: Windows and "executable" |
| 527 modebits = stat.S_IMODE(mode) | 588 modebits = stat.S_IMODE(mode) |
| 528 modestr = "%o" % (modebits,) | 589 modestr = "%o" % (modebits,) |
| 529 if not modestr.startswith("0"): | 590 if not modestr.startswith("0"): |
| 544 if not isinstance(what, bytes): | 605 if not isinstance(what, bytes): |
| 545 what = what.encode("ascii") | 606 what = what.encode("ascii") |
| 546 if what == b"TIMESTAMP": | 607 if what == b"TIMESTAMP": |
| 547 assert filename is None | 608 assert filename is None |
| 548 return util.interpolate_bytes(b"TIMESTAMP = %d%s", value, ls) | 609 return util.interpolate_bytes(b"TIMESTAMP = %d%s", value, ls) |
| 549 if what in (b"ISOTIMESTAMP", b"FLAGS", b"VERSION"): | 610 if what in (b"ISOTIMESTAMP", b"FLAGS", b"VERSION", b"CRC32"): |
| 550 assert filename is None | 611 assert filename is None |
| 551 if not isinstance(value, bytes): | 612 if not isinstance(value, bytes): |
| 552 value = value.encode("ascii") | 613 value = value.encode("ascii") |
| 553 return util.interpolate_bytes(b"%s = %s%s", what, value, ls) | 614 return util.interpolate_bytes(b"%s = %s%s", what, value, ls) |
| 554 assert filename is not None | 615 assert filename is not None |
