| """ |
| 网络工具 |
| 处理Git克隆、文件下载等 |
| """ |
| import os |
| import tempfile |
| import logging |
| from pathlib import Path |
| from typing import Optional, Tuple, Callable |
| from urllib.parse import urlparse |
| import subprocess |
|
|
| logger = logging.getLogger(__name__) |
|
|
| class NetworkUtils: |
| """网络工具类""" |
| |
| @staticmethod |
| def clone_git_repository(repo_url: str, target_dir: Path, branch: str = "main") -> Tuple[bool, str]: |
| """克隆Git仓库""" |
| try: |
| |
| if not NetworkUtils._is_valid_git_url(repo_url): |
| return False, "无效的Git仓库URL" |
| |
| |
| if target_dir.exists(): |
| |
| if any(target_dir.iterdir()): |
| import shutil |
| shutil.rmtree(target_dir) |
| |
| |
| cmd = ["git", "clone", "--branch", branch, "--depth", "1", repo_url, str(target_dir)] |
| |
| result = subprocess.run( |
| cmd, |
| capture_output=True, |
| text=True, |
| timeout=300 |
| ) |
| |
| if result.returncode == 0: |
| logger.info(f"成功克隆Git仓库: {repo_url} -> {target_dir}") |
| return True, "克隆成功" |
| else: |
| error_msg = result.stderr.strip() |
| logger.error(f"克隆Git仓库失败: {error_msg}") |
| return False, f"克隆失败: {error_msg}" |
| |
| except subprocess.TimeoutExpired: |
| logger.error("克隆Git仓库超时") |
| return False, "克隆超时,请检查网络连接" |
| except Exception as e: |
| logger.error(f"克隆Git仓库异常: {e}") |
| return False, f"克隆异常: {str(e)}" |
| |
| @staticmethod |
| def _is_valid_git_url(url: str) -> bool: |
| """验证Git URL格式""" |
| try: |
| parsed = urlparse(url) |
| if parsed.scheme in ('http', 'https', 'git'): |
| return True |
| |
| if url.startswith('git@') and '.git' in url: |
| return True |
| return False |
| except Exception: |
| return False |
| |
| @staticmethod |
| def download_file(url: str, target_path: Path) -> Tuple[bool, str]: |
| """下载文件""" |
| try: |
| import requests |
| |
| response = requests.get(url, stream=True, timeout=30) |
| response.raise_for_status() |
| |
| with open(target_path, 'wb') as f: |
| for chunk in response.iter_content(chunk_size=8192): |
| f.write(chunk) |
| |
| logger.info(f"成功下载文件: {url} -> {target_path}") |
| return True, "下载成功" |
| |
| except requests.RequestException as e: |
| logger.error(f"下载文件失败: {e}") |
| return False, f"下载失败: {str(e)}" |
| except Exception as e: |
| logger.error(f"下载文件异常: {e}") |
| return False, f"下载异常: {str(e)}" |
| |
| @staticmethod |
| def get_git_info(repo_url: str) -> Tuple[bool, dict]: |
| """获取Git仓库信息""" |
| try: |
| with tempfile.TemporaryDirectory() as temp_dir: |
| temp_path = Path(temp_dir) |
| |
| |
| success, message = NetworkUtils.clone_git_repository(repo_url, temp_path) |
| if not success: |
| return False, {"error": message} |
| |
| |
| from app.utils.file_utils import file_utils |
| valid, msg, metadata = file_utils.validate_plugin_structure(temp_path) |
| |
| if valid: |
| return True, { |
| "name": metadata.get("name"), |
| "version": metadata.get("version"), |
| "description": metadata.get("description"), |
| "author": metadata.get("author") |
| } |
| else: |
| return False, {"error": msg} |
| |
| except Exception as e: |
| logger.error(f"获取Git信息失败: {e}") |
| return False, {"error": str(e)} |
|
|
| |
| network_utils = NetworkUtils() |