diff --git a/client-file-collector/client_file_collector.py b/client-file-collector/client_file_collector.py index a837370..6b8cff9 100644 --- a/client-file-collector/client_file_collector.py +++ b/client-file-collector/client_file_collector.py @@ -715,22 +715,14 @@ class CollectorApp: target = self._build_unique_backup_target(backup_dir, file_path) - try: - shutil.move(str(file_path), str(target)) + if self._move_file_transactional(file_path, target, row_index): self.log("第%d行源文件已转移至备份: %s -> %s" % (row_index, file_path.name, target)) return True - except FileNotFoundError: - self.log("第%d行源文件在备份时已被处理,忽略重复备份: %s" % (row_index, file_path.name)) - return True - except Exception as e: - self.log("第%d行源文件备份转移失败: %s, 原因: %s" % (row_index, file_path.name, e)) - return False - def _build_unique_backup_target(self, backup_dir, file_path): - target = backup_dir / file_path.name - if not target.exists(): - return target + self.log("第%d行源文件备份转移失败: %s" % (row_index, file_path.name)) + return False + def _build_unique_backup_target(self, backup_dir, file_path): timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f") base_name = file_path.stem suffix = file_path.suffix @@ -741,6 +733,56 @@ class CollectorApp: index += 1 return candidate + def _move_file_transactional(self, source_file, target_file, row_index): + try: + source_file.rename(target_file) + return True + except FileNotFoundError: + self.log("第%d行源文件在备份时已被处理,忽略重复备份: %s" % (row_index, source_file.name)) + return True + except Exception: + pass + + temp_target = target_file.parent / ("%s.__tmp__.%d" % (target_file.name, int(time.time() * 1000))) + try: + shutil.copy2(str(source_file), str(temp_target)) + if target_file.exists(): + target_file = self._build_unique_backup_target(target_file.parent, source_file) + temp_target.replace(target_file) + except Exception as e: + try: + if temp_target.exists(): + temp_target.unlink() + except Exception: + pass + self.log("第%d行复制到备份目录失败: %s, 原因: %s" % (row_index, source_file.name, e)) + return False + + if self._delete_source_with_retry(source_file): + return True + + try: + if target_file.exists(): + target_file.unlink() + except Exception as rollback_error: + self.log("第%d行回滚备份文件失败: %s, 原因: %s" % (row_index, target_file, rollback_error)) + return False + + def _delete_source_with_retry(self, file_path, retry_times=6, retry_interval=0.3): + for idx in range(max(1, retry_times)): + try: + file_path.unlink() + return True + except FileNotFoundError: + return True + except Exception as e: + if idx < retry_times - 1: + time.sleep(retry_interval) + continue + self.log("删除源文件失败: %s, 原因: %s" % (file_path.name, e)) + return False + return False + def _upload_file(self, row, file_path): url = self._base_url() + "/collector/client/upload" data = { diff --git a/client-file-collector/dist/QMSFileCollector.exe b/client-file-collector/dist/QMSFileCollector.exe index 95ac308..51967f3 100644 Binary files a/client-file-collector/dist/QMSFileCollector.exe and b/client-file-collector/dist/QMSFileCollector.exe differ