diff --git a/example.yaml b/example.yaml index a1fefd6..a911a3c 100644 --- a/example.yaml +++ b/example.yaml @@ -2,9 +2,9 @@ dest: /path/to/store/backup/files # The programs will store database and backup enable_pcre2: false # Optional. Default value: false. Try to use PCRE2 first. PCRE2 may be a little slower than internal regex library. remove_old_files: true # Optional. Default value: true. Remove unneeded backup files which already deleted in source tree when backuping files. ignore_hidden_files: true # Optional. Default value: true. Whether to ignore files which its name starts with ".". Only effect folder which type is "path". -compress_method: "bzip2" # Optional. Default value: null. Supported value: "bzip2", "gzip", "lzma", "lzip", "zstd", "snappy" +compress_method: "bzip2" # Optional. Default value: null. Supported value: "bzip2", "gzip", "lzma", "lzip", "zstd", "snappy", "brotli" # Optional. Default value: null. bzip2 support 1-9 (Default: 9). gzip support 0-9 (Default: 9). lzma or lzip support 0-9 (Default: 6). -# zstd support 0-22 (Default: 3). +# zstd support 0-22 (Default: 3). brotli support 0-11 (Default: unset). compress_level: 6 programs: - name: Your program name # This name is used to identify different application. diff --git a/game_backuper/compress.py b/game_backuper/compress.py index ee6c4b7..3fbe0ac 100644 --- a/game_backuper/compress.py +++ b/game_backuper/compress.py @@ -37,6 +37,14 @@ try: have_snappy = True except ImportError: have_snappy = False +try: + from brotli import ( + Compressor as BrotliCompressor, + Decompressor as BrotliDecompressor, + ) + have_brotli = True +except ImportError: + have_brotli = False from enum import IntEnum, unique try: from functools import cached_property @@ -54,6 +62,7 @@ class CompressMethod(IntEnum): LZIP = 3 ZSTD = 4 SNAPPY = 5 + BROTLI = 6 @staticmethod def from_str(v: str) -> IntEnum: @@ -71,6 +80,8 @@ class CompressMethod(IntEnum): return CompressMethod.ZSTD elif t == "snappy": return CompressMethod.SNAPPY + elif t == "brotli": + return CompressMethod.BROTLI else: raise TypeError('Must be str.') @@ -140,6 +151,17 @@ class CompressConfig: raise NotImplementedError("snappy not supported.") self._level = None self._ext = ".snappy" + elif self._method == CompressMethod.BROTLI: + if not have_brotli: + raise NotImplementedError("brotli not supported.") + if level is None: + self._level = None + else: + if isinstance(level, int) and level >= 0 and level <= 11: + self._level = level + else: + raise ValueError('brotli: compress_level should be 0-11.') + self._ext = '.br' self._chunk_size = 131072 def __repr__(self): @@ -176,6 +198,8 @@ if have_zstd: supported_exts.append('.zst') if have_snappy: supported_exts.append('.snappy') +if have_brotli: + supported_exts.append('.br') def sizeof_fmt(num, suffix='B'): @@ -247,6 +271,21 @@ def compress(src: str, dest: str, c: CompressConfig, name: str, prog: str): f.write(b) a = t.read(cs) del a, b, o + elif c.method == CompressMethod.BROTLI: + k = {} + if c.level is not None: + k['quality'] = c.level + with open(src, 'rb') as t: + with open(fn, 'wb') as f: + o = BrotliCompressor(**k) + a = t.read(cs) + while a != b'': + b = o.process(a) + f.write(b) + a = t.read(cs) + f.write(o.finish()) + del a, b, o + del k i = compress_info(getsize(src), getsize(fn)) print(f'{prog}: Compressed {src}({name}) -> {fn} ({i})') del i @@ -311,5 +350,17 @@ def decompress(src: str, dest: str, c: CompressConfig, name: str, prog: str): a = f.read(cs) o.flush() del a, b, o + elif c.method == CompressMethod.BROTLI: + with open(fn, 'rb') as f: + with open(dest, 'wb') as t: + o = BrotliDecompressor() + a = f.read(cs) + while a != b'': + b = o.process(a) + t.write(b) + a = f.read(cs) + if not o.is_finished(): + raise ValueError('Read all datas from file but seems not finished.') # noqa: E501 + del a, b, o i = compress_info(getsize(dest), getsize(fn)) print(f'{prog}: Decompressed {fn}({name}) -> {dest} ({i})') diff --git a/requirements.txt b/requirements.txt index 9004315..d9fd094 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ pyyaml plyvel[leveldb] lzip[lzip] python-snappy[snappy] +brotli[brotli] diff --git a/setup.py b/setup.py index a555448..ad75210 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ else: "leveldb": "plyvel", "lzip": "lzip", "snappy": "python-snappy", + "brotli": "brotli", }, "python_requires": ">=3.6" }