From ea645a8ec9eebf40891e0b371eba4d52d0bae49c Mon Sep 17 00:00:00 2001 From: lifegpc Date: Thu, 16 Feb 2023 13:31:31 +0800 Subject: [PATCH] Add support to protect file name --- example.yaml | 4 +++ game_backuper/backuper.py | 59 +++++++++++++++++++++++++++++++++++---- game_backuper/config.py | 36 +++++++++++++++++++++++- game_backuper/db.py | 5 ++-- game_backuper/restorer.py | 4 +++ 5 files changed, 100 insertions(+), 8 deletions(-) diff --git a/example.yaml b/example.yaml index 979c176..297aacf 100644 --- a/example.yaml +++ b/example.yaml @@ -9,6 +9,7 @@ compress_level: 6 encrypt_db: false # Optional. Default value: false. Encrypt the database. Warning: The default python sqlite library don't support encrypt, it just ignore encrypt phases. db_password: "Password" # Specify the password of the encryped database. encrypt_files: false # Optional. Default value: false. Encrypt backup files. The key information will stored in database. +protect_filename: false # Optional. Default value: false. Use id in database as file name. Only works when encrypt_files is true. programs: - name: Your program name # This name is used to identify different application. base: /path/to/save/path # Must be absoulte path. @@ -18,6 +19,7 @@ programs: compress_method: null # Optional. compress_level: null # Optional. encrypt_files: false # Optional + protect_filename: false # Optional files: - BGI.gdb # path to a file/folder. All subfolders will include if it is a folder. Must be relative path. - type: path @@ -29,6 +31,7 @@ programs: compress_method: null # Optional. compress_level: null # Optional. encrypt_files: false # Optional. + protect_filename: false # Optional excludes: # Optional. Exculde some files. Only effected when path is a folder. - data.db # Releative path - /path/to/data.db # Absolute path @@ -51,5 +54,6 @@ programs: compress_method: null # Optional. compress_level: null # Optional. encrypt_files: false # Optional. + protect_filename: false # Optional domains: # optional. Just backup minor domains in localstorage database. Only chromium is tested. - some domain diff --git a/game_backuper/backuper.py b/game_backuper/backuper.py index 72d70bf..5fe6f14 100644 --- a/game_backuper/backuper.py +++ b/game_backuper/backuper.py @@ -8,7 +8,8 @@ from game_backuper.config import ( from game_backuper.cml import Opts, OptAction from threading import Thread from os.path import exists, join, isdir -from os import mkdir, remove, close +from os import remove, close +from shutil import move from game_backuper.file import new_file, copy_file, File, mkdir_for_file from game_backuper.filetype import FileType from game_backuper.restorer import RestoreTask @@ -30,8 +31,7 @@ class BackupTask(Thread): prog = self.prog.name bp = join(self.cfg.dest, prog) ebp = join(self.cfg.dest, '.encrypt', prog) - if not exists(bp): - mkdir(bp) + ebpi = join(self.cfg.dest, '.encrypt', '.id') fl = self.db.get_file_list(prog) for f in self.prog.files: if isinstance(f, ConfigNormalFile): @@ -45,12 +45,25 @@ class BackupTask(Thread): continue de = join(ebp if f.encrypt_files else bp, f[0]) if ori is not None: + de2 = join(ebpi if f.encrypt_files else bp, str(ori.id)) # noqa: E501 + if f.protect_filename: + if not exists(de2) and exists(de): + mkdir_for_file(de2) + move(de, de2) + print(f'{prog}: Renamed {de} -> {de2}.') + de = de2 + else: + if not exists(de) and exists(de2): + mkdir_for_file(de) + move(de2, de) + print(f'{prog}: Renamed {de2} -> {de}.') if ori.size == nf.size and ori.hash == nf.hash: if c is None: if exists(de) and not f.encrypt_files: print(f'{prog}: Skip {f[0]}.') remove_compress_files(de, prog, f.name) self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501 + self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501 continue elif exists(de) and f.encrypt_files and not ori.compressed: # noqa: E501 print(f'{prog}: Skip {f[0]}.') @@ -61,6 +74,7 @@ class BackupTask(Thread): print(f'{prog}: Skip {f.name}.') remove_compress_files(de, prog, f.name, c.ext) # noqa: E501 self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501 + self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501 continue elif f.encrypt_files and ori.compressed_type == c.method: # noqa: E501 print(f'{prog}: Skip {f.name}.') @@ -74,12 +88,18 @@ class BackupTask(Thread): copy_file(f[1], de, f[0], prog) remove_compress_files(de, prog, f.name) self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501 + self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501 else: compress(f[1], de, c, f.name, prog) self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501 + self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501 self.db.set_file(ori.id, nf.size, nf.hash) self.db.set_file_encrypt_information(ori.id, stats) else: + if f.protect_filename: + self.db.add_file(nf, False) + tmpori = self.db.get_file(prog, f[0]) + de = join(ebpi if f.encrypt_files else bp, str(tmpori.id)) # noqa: E501 if f.encrypt_files: s = encrypt_file(f[1], de, nf, f.name, prog, c) nf = File.from_encrypt_stats(s, nf) @@ -91,7 +111,10 @@ class BackupTask(Thread): else: compress(f[1], de, c, f.name, prog) self.remove_encrypted_file(join(ebp, f[0]), prog, f.name, ori) # noqa: E501 - self.db.add_file(nf) + if f.protect_filename: + self.db.set_file_encrypt_information(tmpori.id, s) + else: + self.db.add_file(nf) elif isinstance(f, ConfigLeveldb): from game_backuper.leveldb import have_leveldb if not have_leveldb: @@ -111,12 +134,25 @@ class BackupTask(Thread): c = f.compress_config de = join(ebp if f.encrypt_files else bp, f.name + ".db") if ori is not None: + de2 = join(ebpi if f.encrypt_files else bp, str(ori.id)) # noqa: E501 + if f.protect_filename: + if not exists(de2) and exists(de): + mkdir_for_file(de2) + move(de, de2) + print(f'{prog}: Renamed {de} -> {de2}.') + de = de2 + else: + if not exists(de) and exists(de2): + mkdir_for_file(de) + move(de2, de) + print(f'{prog}: Renamed {de2} -> {de}.') if ori.type is None or ori.type != FileType.LEVELDB: pp = join(bp, ori.file) if exists(pp): remove(pp) remove_compress_files(pp, prog, f.name) self.remove_encrypted_file(join(ebp, ori.file), prog, f.name, ori) # noqa: E501 + self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501 self.db.remove_file(ori) ori = None if ori is not None: @@ -126,6 +162,7 @@ class BackupTask(Thread): print(f'{prog}: Skip {f[0]}.') remove_compress_files(de, prog, f.name) self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501 + self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501 continue elif exists(de) and f.encrypt_files and not ori.compressed: # noqa: E501 print(f'{prog}: Skip {f[0]}.') @@ -136,18 +173,27 @@ class BackupTask(Thread): print(f'{prog}: Skip {f.name}.') remove_compress_files(de, prog, f.name, c.ext) self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501 + self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501 continue elif f.encrypt_files and ori.compressed_type == c.method: # noqa: E501 print(f'{prog}: Skip {f.name}.') remove_unencryped_files(join(bp, f.name + '.db'), prog, f.name) # noqa: E501 continue + if f.protect_filename: + ori = self.db.get_file(prog, f[0]) + if ori is None: + nf = File(None, f.name, 0, prog, None, FileType.LEVELDB, None, None, None, None, None) # noqa: E501 + self.db.add_file(nf, False) + ori = self.db.get_file(prog, f[0]) + de = join(ebpi if f.encrypt_files else bp, str(ori.id)) # noqa: E501 mkdir_for_file(de) st = None - if c is None: + if c is None and not f.encrypt_files: leveldb_to_sqlite(f.full_path, de, ent) print(f'{prog}: Covert leveldb done. {f.full_path}({f.name}) -> {de}') # noqa: E501 remove_compress_files(de, prog, f.name) self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501 + self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501 else: tmp = mkstemp() close(tmp[0]) @@ -160,6 +206,7 @@ class BackupTask(Thread): else: compress(tmp, de, c, f.name, prog) self.remove_encrypted_file(join(ebp, f[0] + '.db'), prog, f.name, ori) # noqa: E501 + self.remove_encrypted_file(join(ebpi, str(ori.id)), prog, f.name, ori) # noqa: E501 remove(tmp) print(f'{prog}: Removed tempfile {tmp}') if ori is None: @@ -180,6 +227,7 @@ class BackupTask(Thread): print(f'{prog}: Remove {de}({fn})') remove_compress_files(de, prog, fn) self.remove_encrypted_file(join(ebp, fn), prog, fn, f) + self.remove_encrypted_file(join(ebpi, str(f.id)), prog, fn, f) self.db.remove_file(f) if f.type == FileType.LEVELDB: de = join(bp, fn + '.db') @@ -188,6 +236,7 @@ class BackupTask(Thread): print(f'{prog}: Remove {de}({fn})') remove_compress_files(de, prog, fn + '.db') self.remove_encrypted_file(join(ebp, fn + '.db'), prog, fn, f) + self.remove_encrypted_file(join(ebpi, str(f.id)), prog, fn, ori) # noqa: E501 self.db.remove_file(f) def remove_encrypted_file(self, loc: str, prog: str, name: str, f: File): diff --git a/game_backuper/config.py b/game_backuper/config.py index 252dd5e..ccc7294 100644 --- a/game_backuper/config.py +++ b/game_backuper/config.py @@ -21,6 +21,7 @@ class BasicOption: _enable_pcre2 = None _encrypt_files = None _compress_config = None + _protect_filename = None @property def compress_config(self) -> CompressConfig: @@ -64,6 +65,20 @@ class BasicOption: return cfg._encrypt_files return False + @cached_property + def protect_filename(self) -> bool: + if self._protect_filename is not None: + return self._protect_filename and self.encrypt_files + prog = getattr(self, "_prog", None) + if prog is not None: + if prog._protect_filename is not None: + return prog._protect_filename and self.encrypt_files + cfg = getattr(self, "_cfg", None) + if cfg is not None: + if cfg._protect_filename is not None: + return cfg._protect_filename and self.encrypt_files + return False + @cached_property def remove_old_files(self) -> bool: if self._remove_old_files is not None: @@ -83,6 +98,7 @@ class BasicOption: self.parse_remove_old_files(data) self.parse_enable_pcre2(data) self.parse_encrypt_files(data) + self.parse_protect_filename(data) def parse_compress_config(self, data=None): if data is None: @@ -116,6 +132,17 @@ class BasicOption: raise TypeError('encrypt_files option must be a boolean.') del v + def parse_protect_filename(self, data=None): + if data is None: + data = getattr(self, 'data') + if 'protect_filename' in data: + v = data['protect_filename'] + if isinstance(v, bool): + self._protect_filename = v + else: + raise TypeError('protect_filename option must be a boolean.') + del v + def parse_remove_old_files(self, data=None): if data is None: data = getattr(self, 'data') @@ -438,7 +465,14 @@ class Program(BasicOption, NFBasicOption): continue return r elif t == 'leveldb': - if relpath(i['name'], name) == '.': + if isabs(i['path']): + n = i['name'] + else: + n = i['path'] + if 'name' in i and isinstance(i['name'], str): + if i['name'] != '': + n = i['name'] + if relpath(n, name) == '.': return ConfigOLeveldb(i, self._cfg, self) @cached_property diff --git a/game_backuper/db.py b/game_backuper/db.py index 0cf87f7..767f735 100644 --- a/game_backuper/db.py +++ b/game_backuper/db.py @@ -171,7 +171,7 @@ class Db: tuple(self.VERSION)) self.db.commit() - def add_file(self, f: File): + def add_file(self, f: File, commited: bool = True): with self._lock: self.db.execute('INSERT INTO files (file, size, program, hash) VALUES (?, ?, ?, ?);', # noqa: E501 (f.file, f.size, f.program, f.hash)) @@ -188,7 +188,8 @@ class Db: (f.program, f.file)) for i in cur: self.db.execute('INSERT INTO encrypted_files VALUES (?, ?, ?, ?, ?, ?);', (i[0], f.key, f.iv, f.crc32, f.x_compress_type, f.compressed_size)) # noqa: E501 - self.db.commit() + if commited: + self.db.commit() @property def encrypted(self): diff --git a/game_backuper/restorer.py b/game_backuper/restorer.py index f7dd36f..81aa909 100644 --- a/game_backuper/restorer.py +++ b/game_backuper/restorer.py @@ -39,6 +39,8 @@ class RestoreTask(Thread): nam = r.real_name if f.encrypted: src = join(self.cfg.dest, '.encrypt', prog, fn) + if not exists(src): + src = join(self.cfg.dest, '.encrypt', '.id', str(f.id)) # noqa: E501 else: src = join(self.cfg.dest, prog, fn) c = r.compress_config @@ -78,6 +80,8 @@ class RestoreTask(Thread): nam = r.real_name if f.encrypted: src = join(self.cfg.dest, '.encrypt', prog, fn + '.db') + if not exists(src): + src = join(self.cfg.dest, '.encrypt', '.id', str(f.id)) # noqa: E501 else: src = join(self.cfg.dest, prog, fn + '.db') c = r.compress_config