diff --git a/example.yaml b/example.yaml index 1608adb..671d74d 100644 --- a/example.yaml +++ b/example.yaml @@ -1,15 +1,18 @@ dest: /path/to/store/backup/files # The programs will store database and backup files in this location 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". programs: - name: Your program name # This name is used to identify different application. base: /path/to/save/path # Must be absoulte path. remove_old_files: true # Optional. + ignore_hidden_files: true # Optional. files: - BGI.gdb # path to a file/folder. All subfolders will include if it is a folder. Must be relative path. - type: path path: folder # path to a file/folder. All subfolders will include if it is a folder. Must be relative path if name not found. name: folder2 # optional. path to the backup files. Shoule be a relative path remove_old_files: true # Optional. + ignore_hidden_files: true # Optional. - type: leveldb # module plyvel is needed to support this type. This will store leveldb database to a single file database (sqlite3) path: leveldb # path to leveldb. Must be relative path. name: dest # optional. path to the backup files. Shoule be a relative path diff --git a/game_backuper/config.py b/game_backuper/config.py index 6e337e4..add80ae 100644 --- a/game_backuper/config.py +++ b/game_backuper/config.py @@ -14,6 +14,7 @@ except ImportError: class BasicOption: + '''Basic options which is included in config, program and files.''' _remove_old_files = None @cached_property @@ -45,6 +46,43 @@ class BasicOption: del v +class NFBasicOption: + """Basic options which is included in config, program.""" + _ignore_hidden_files = None + + def __init__(self, cfg=None, prog=None): + self._cfg = cfg + self._prog = prog + + @cached_property + def ignore_hidden_files(self) -> bool: + if self._ignore_hidden_files is not None: + return self._ignore_hidden_files + prog = getattr(self, "_prog", None) + if prog is not None: + if prog._ignore_hidden_files is not None: + return prog._ignore_hidden_files + cfg = getattr(self, "_cfg", None) + if cfg is not None: + if cfg._ignore_hidden_files is not None: + return cfg._ignore_hidden_files + return True + + def parse_all_nf(self, data=None): + self.parse_ignore_hidden_files(data) + + def parse_ignore_hidden_files(self, data=None): + if data is None: + data = getattr(self, 'data') + if 'ignore_hidden_files' in data: + v = data['ignore_hidden_files'] + if isinstance(v, bool): + self._ignore_hidden_files = v + else: + raise TypeError('ignore_hidden_files option must be a bool.') + del v + + def namedtuple_bo(typename, field_names): a = namedtuple(typename, field_names) return type(typename, (a, BasicOption), {}) @@ -55,12 +93,13 @@ ConfigLeveldb = namedtuple_bo('ConfigLeveldb', ['name', 'full_path', 'domains']) ConfigResult = Union[ConfigNormalFile, ConfigLeveldb] -class Program(BasicOption): +class Program(BasicOption, NFBasicOption): def __init__(self, data: dict, cfg): self.data = data self._files = None self._cfg = cfg self.parse_all() + self.parse_all_nf() @cached_property def base(self) -> str: @@ -94,9 +133,13 @@ class Program(BasicOption): raise ValueError('Absolute path must need a name.') bp = join(b, i) if isfile(bp): - r.append(ConfigNormalFile(i, bp)) + tname = relpath(join(b, i), b) + r.append(ConfigNormalFile(tname, bp)) + del tname elif isdir(bp): - ll = listdirs(bp) + top = NFBasicOption(self._cfg, self) + ll = listdirs(bp, top.ignore_hidden_files) + del top for ii in ll: r.append(ConfigNormalFile(relpath(ii, b), ii)) elif isinstance(i, dict): @@ -114,13 +157,20 @@ class Program(BasicOption): if i['name'] != '': name = i['name'] if isfile(bp): - tmp = ConfigNormalFile(name, bp) + tname = relpath(join(b, name), b) + tmp = ConfigNormalFile(tname, bp) + del tname tmp.parse_all(i) r.append(tmp) elif isdir(bp): - ll = listdirs(bp) + top = NFBasicOption(self._cfg, self) + top.parse_ignore_hidden_files(i) + ll = listdirs(bp, top.ignore_hidden_files) + del top for ii in ll: - tmp = ConfigNormalFile(join(name, relpath(ii, bp)), ii) # noqa: E501 + tname = relpath(join(b, join(name, relpath(ii, bp))), b) # noqa: E501 + tmp = ConfigNormalFile(tname, ii) + del tname tmp.parse_all(i) r.append(tmp) elif t == 'leveldb': @@ -143,10 +193,13 @@ class Program(BasicOption): dms.append(ii.encode()) if len(dms) == 0: dms = None - tmp = ConfigLeveldb(n, p, dms) + tname = relpath(join(b, n), b) + tmp = ConfigLeveldb(tname, p, dms) + del tname tmp.parse_all(i) r.append(tmp) for i in r: + i._cfg = self._cfg i._prog = self return r @@ -158,7 +211,7 @@ class Program(BasicOption): return v -class Config(BasicOption): +class Config(BasicOption, NFBasicOption): dest = '' progs = [] progs_name = [] @@ -176,6 +229,7 @@ class Config(BasicOption): if 'programs' not in t: raise ValueError("No programs found.") self.parse_all(t) + self.parse_all_nf(t) progs = t['programs'] if not isinstance(progs, list): raise ValueError("programs should be list.") diff --git a/game_backuper/file.py b/game_backuper/file.py index 76407d5..a140c49 100644 --- a/game_backuper/file.py +++ b/game_backuper/file.py @@ -21,12 +21,13 @@ def copy_file(loc: str, dest: str, name: str, prog: str): print(f'{prog}: Copyed {loc}({name}) -> {r}') -def listdirs(loc: str): +def listdirs(loc: str, ignore_hidden_files: bool = True): bl = listdir(loc) r = [] for i in bl: if i.startswith('.'): - continue + if ignore_hidden_files or i == '.' or i == '..': + continue p = join(loc, i) if isfile(p): r.append(p)