diff --git a/README.md b/README.md index 017915f..d32977b 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,12 @@ mcd_root/ 是否在备份时临时关闭自动保存 +### copy_on_write + +默认值: `false` + +使用某些文件系统的写时拷贝(一种增量备份) + ### ignored_files 默认值: diff --git a/README_en.md b/README_en.md index cca7717..15bf15e 100644 --- a/README_en.md +++ b/README_en.md @@ -105,6 +105,12 @@ Default: `true` If turn off auto save when making backup or not +### copy_on_write + +Default: `false` + +Useing copy_on_write in some File system(incremental backup) + ### ignored_files If ignore file `session.lock` during backup, which can diff --git a/quick_backup_multi/__init__.py b/quick_backup_multi/__init__.py index 4befbe5..ebdd796 100644 --- a/quick_backup_multi/__init__.py +++ b/quick_backup_multi/__init__.py @@ -76,6 +76,30 @@ def get_backup_file_name(backup_format: BackupFormat): raise ValueError('unknown backup mode {}'.format(backup_format)) +copy_file_range_supported=hasattr(os, "copy_file_range") +COW_COPY_BUFFER_SIZE = 2**30 # 1GB / need int, may overflow, so cannot copy files larger than 2GB in a single pass + +#copy using "Copy On Write" +def _cpcow(src_path: str, dst_path: str): + if not copy_file_range_supported or not config.copy_on_write: + return shutil.copy2(src_path, dst_path) + + if os.path.isdir(dst_path): + dst_path = os.path.join(dst_path, os.path.basename(src_path)) + + try: + with open(src_path,'rb') as fsrc, open(dst_path,'wb+') as fdst: + while os.copy_file_range(fsrc.fileno(), fdst.fileno(), COW_COPY_BUFFER_SIZE): + pass + + except Exception as e: + server_inst.logger.warning(str(e) + str(src_path) + "->" + str(dst_path) + ",Retry with other functions") + shutil.copy(src_path, dst_path) + + shutil.copystat(src_path, dst_path) # copy2 + return dst_path + + def copy_worlds(src: str, dst: str, intent: CopyWorldIntent, *, backup_format: Optional[BackupFormat] = None): if backup_format is None: backup_format = get_backup_format() @@ -96,12 +120,12 @@ def copy_worlds(src: str, dst: str, intent: CopyWorldIntent, *, backup_format: O server_inst.logger.info('copying {} -> {}'.format(src_path, dst_path)) if os.path.isdir(src_path): - shutil.copytree(src_path, dst_path, ignore=lambda path, files: set(filter(config.is_file_ignored, files))) + shutil.copytree(src_path, dst_path, ignore=lambda path, files: set(filter(config.is_file_ignored, files)), copy_function=_cpcow) elif os.path.isfile(src_path): dst_dir = os.path.dirname(dst_path) if not os.path.isdir(dst_dir): os.makedirs(dst_dir) - shutil.copy(src_path, dst_path) + _cpcow(src_path, dst_path) else: server_inst.logger.warning('{} does not exist while copying ({} -> {})'.format(src_path, src_path, dst_path)) elif backup_format == BackupFormat.tar or backup_format == BackupFormat.tar_gz: diff --git a/quick_backup_multi/config.py b/quick_backup_multi/config.py index ff22adc..e63d8b9 100644 --- a/quick_backup_multi/config.py +++ b/quick_backup_multi/config.py @@ -10,6 +10,7 @@ class SlotInfo(Serializable): class Configuration(Serializable): size_display: bool = True turn_off_auto_save: bool = True + copy_on_write: bool = False ignored_files: List[str] = [ 'session.lock' ]