From 9eddb3133c03d3487170b5d8958b3250fec22a91 Mon Sep 17 00:00:00 2001 From: tech Date: Thu, 7 Aug 2025 18:03:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8DCDN=20URL=E5=92=8C=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E6=9E=84=E5=BB=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修复CDN URL构建,正确处理路径重复问题 - 修复rclone路径构建,使用正确的格式: {rclone_remote}:{storage_path}/{version_path}/{filename} - 修复CDN路径构建,使用正确的格式: {cdn_base_url}/{version_path}/{filename} - 修复CDN内容解密处理,支持加密和解密后的内容 - 添加路径构建测试示例 - 完善错误处理和空内容处理 --- PROJECT_STRUCTURE.md | 1 + config-man.json | 25 +++ docs/USAGE.md | 93 ++++++++++- examples/config_usage.py | 58 +++++++ examples/test_cdn_url.py | 50 ++++++ examples/test_paths.py | 53 ++++++ examples/test_rclone_path.py | 48 ++++++ src/config_man/cli/main.py | 66 +++++++- src/config_man/core/config_manager.py | 54 ++++-- src/config_man/core/display_manager.py | 11 +- src/config_man/utils/__init__.py | 5 +- src/config_man/utils/config.py | 222 +++++++++++++++++++++++++ src/config_man/utils/mock_rclone.py | 21 ++- 13 files changed, 675 insertions(+), 32 deletions(-) create mode 100644 config-man.json create mode 100644 examples/config_usage.py create mode 100644 examples/test_cdn_url.py create mode 100644 examples/test_paths.py create mode 100644 examples/test_rclone_path.py create mode 100644 src/config_man/utils/config.py diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md index 81fd80d..a67c4d3 100644 --- a/PROJECT_STRUCTURE.md +++ b/PROJECT_STRUCTURE.md @@ -43,6 +43,7 @@ config-man/ │ └── basic_usage.py # 基本使用示例 ├── main.py # 主入口文件 ├── pyproject.toml # 项目配置文件 +├── config-man.json # 配置文件 ├── README.md # 项目主文档 ├── .gitignore # Git忽略文件 └── PROJECT_STRUCTURE.md # 项目结构说明(本文件) diff --git a/config-man.json b/config-man.json new file mode 100644 index 0000000..9e2e90d --- /dev/null +++ b/config-man.json @@ -0,0 +1,25 @@ +{ + "storage": { + "path": "n3ft/ab", + "rclone_remote": "ft", + "timeout": 30 + }, + "cdn": { + "base_url": "https://ab.bamboogames.cc/ab", + "timeout": 10, + "retry_count": 3 + }, + "crypto": { + "algorithm": "DES", + "key": "tbambooz" + }, + "logging": { + "level": "INFO", + "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + }, + "display": { + "table_format": "grid", + "max_width": 100, + "truncate_length": 25 + } +} diff --git a/docs/USAGE.md b/docs/USAGE.md index bc787ef..188d201 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -43,10 +43,72 @@ python main.py view --help - `--version`: 版本号,格式为 0.29, 0.30, 0.31 等(必需) - `--json`: 以JSON格式输出(可选) -- `--storage-path`: 对象存储路径前缀,默认为 'ab'(可选) -- `--cdn-url`: CDN基础URL(可选) +- `--storage-path`: 对象存储路径前缀(覆盖配置文件) +- `--cdn-url`: CDN基础URL(覆盖配置文件) - `--mock`: 使用模拟环境进行测试(可选) +### 配置管理命令 + +- `show-config`: 显示当前配置 +- `set-config`: 设置配置值 + +## 配置文件 + +Config-Man 支持多种配置方式: + +### 1. 配置文件 + +默认配置文件位置: +- 用户配置: `~/.config/config-man/config-man.json` +- 项目配置: `./config-man.json` + +配置文件格式: +```json +{ + "storage": { + "path": "ab", + "rclone_remote": "remote", + "timeout": 30 + }, + "cdn": { + "base_url": "", + "timeout": 10, + "retry_count": 3 + }, + "crypto": { + "algorithm": "DES", + "key": "tbambooz" + }, + "display": { + "table_format": "grid", + "max_width": 80, + "truncate_length": 25 + } +} +``` + +### 2. 环境变量 + +支持以下环境变量: + +| 环境变量 | 配置项 | 说明 | +|---------|--------|------| +| `CONFIG_MAN_STORAGE_PATH` | `storage.path` | 存储路径 | +| `CONFIG_MAN_STORAGE_RCLONE_REMOTE` | `storage.rclone_remote` | rclone远程名称 | +| `CONFIG_MAN_STORAGE_TIMEOUT` | `storage.timeout` | 存储操作超时时间 | +| `CONFIG_MAN_CDN_BASE_URL` | `cdn.base_url` | CDN基础URL | +| `CONFIG_MAN_CDN_TIMEOUT` | `cdn.timeout` | CDN请求超时时间 | +| `CONFIG_MAN_CDN_RETRY_COUNT` | `cdn.retry_count` | CDN重试次数 | +| `CONFIG_MAN_CRYPTO_KEY` | `crypto.key` | 加密密钥 | +| `CONFIG_MAN_CRYPTO_ALGORITHM` | `crypto.algorithm` | 加密算法 | +| `CONFIG_MAN_DISPLAY_TABLE_FORMAT` | `display.table_format` | 表格格式 | +| `CONFIG_MAN_DISPLAY_MAX_WIDTH` | `display.max_width` | 最大宽度 | +| `CONFIG_MAN_DISPLAY_TRUNCATE_LENGTH` | `display.truncate_length` | 截断长度 | + +### 3. 命令行参数 + +命令行参数会覆盖配置文件和环境变量的设置。 + ## 输出格式 ### 表格格式(默认) @@ -146,16 +208,35 @@ python main.py view --version 0.30 --mock ## 高级用法 -### 自定义存储路径 +### 配置管理 ```bash -python main.py view --version 0.30 --storage-path custom_path +# 显示当前配置 +python main.py show-config + +# 设置配置值 +python main.py set-config --key storage.path --value custom_path +python main.py set-config --key cdn.base_url --value https://cdn.example.com +python main.py set-config --key display.max_width --value 100 ``` -### 指定CDN URL +### 环境变量配置 ```bash -python main.py view --version 0.30 --cdn-url https://cdn.example.com +# 使用环境变量设置配置 +export CONFIG_MAN_STORAGE_PATH=custom_path +export CONFIG_MAN_CDN_BASE_URL=https://cdn.example.com +export CONFIG_MAN_DISPLAY_MAX_WIDTH=120 + +# 运行命令 +python main.py view --version 0.30 +``` + +### 命令行参数覆盖 + +```bash +# 命令行参数会覆盖配置文件设置 +python main.py view --version 0.30 --storage-path custom_path --cdn-url https://cdn.example.com ``` ### 组合使用 diff --git a/examples/config_usage.py b/examples/config_usage.py new file mode 100644 index 0000000..76e00b3 --- /dev/null +++ b/examples/config_usage.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python3 +""" +Config-Man 配置使用示例 + +演示如何使用配置文件和环境变量。 +""" + +import sys +import os + +# 添加项目根目录到路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from config_man.utils import config + + +def main(): + """配置使用示例""" + print("Config-Man 配置使用示例") + print("=" * 50) + + # 1. 显示当前配置 + print("\n1. 当前配置:") + print(f"存储路径: {config.get_storage_path()}") + print(f"CDN基础URL: {config.get_cdn_base_url()}") + print(f"加密密钥: {config.get_crypto_key()}") + + # 2. 设置配置值 + print("\n2. 设置配置值:") + config.set('storage.path', 'custom_storage') + config.set('cdn.base_url', 'https://cdn.example.com') + config.set('display.max_width', 100) + + print("配置已更新") + + # 3. 显示更新后的配置 + print("\n3. 更新后的配置:") + print(f"存储路径: {config.get_storage_path()}") + print(f"CDN基础URL: {config.get_cdn_base_url()}") + print(f"显示最大宽度: {config.get('display.max_width')}") + + # 4. 保存配置到文件 + print("\n4. 保存配置:") + config.save() + print(f"配置已保存到: {config.config_file}") + print(f"配置文件名称: config-man.json") + + # 5. 环境变量示例 + print("\n5. 环境变量使用示例:") + print("可以通过以下环境变量覆盖配置:") + print(" CONFIG_MAN_STORAGE_PATH=custom_path") + print(" CONFIG_MAN_CDN_BASE_URL=https://cdn.example.com") + print(" CONFIG_MAN_CRYPTO_KEY=custom_key") + print(" CONFIG_MAN_DISPLAY_MAX_WIDTH=120") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/examples/test_cdn_url.py b/examples/test_cdn_url.py new file mode 100644 index 0000000..d281b56 --- /dev/null +++ b/examples/test_cdn_url.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +""" +测试CDN URL构建 + +验证CDN URL的正确构建。 +""" + +import sys +import os + +# 添加项目根目录到路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from config_man.core import ConfigManager + + +def main(): + """测试CDN URL构建""" + print("测试CDN URL构建") + print("=" * 50) + + # 创建配置管理器 + config_manager = ConfigManager() + + # 测试不同版本的CDN URL构建 + versions = ["0.29", "0.30"] + platforms = ["android", "ios"] + + print(f"CDN基础URL: {config_manager.cdn_base_url}") + print(f"存储路径: {config_manager.storage_path}") + print() + + for version in versions: + for platform in platforms: + # 获取CDN URL + cdn_url = config_manager.get_cdn_url(version, platform) + + print(f"版本: {version}, 平台: {platform}") + print(f"CDN URL: {cdn_url}") + print() + + # 显示当前配置 + print(f"当前CDN配置:") + print(f"CDN基础URL: {config_manager.cdn_base_url}") + print(f"CDN超时: {config_manager.cdn_config.get('timeout', 10)}") + print(f"CDN重试次数: {config_manager.cdn_config.get('retry_count', 3)}") + + +if __name__ == "__main__": + main() diff --git a/examples/test_paths.py b/examples/test_paths.py new file mode 100644 index 0000000..bfe23cb --- /dev/null +++ b/examples/test_paths.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +""" +测试路径构建 + +验证rclone路径和CDN路径的正确构建。 +""" + +import sys +import os + +# 添加项目根目录到路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from config_man.core import ConfigManager + + +def main(): + """测试路径构建""" + print("测试路径构建") + print("=" * 50) + + # 创建配置管理器 + config_manager = ConfigManager() + + # 测试不同版本的路径构建 + versions = ["0.29", "0.30"] + platforms = ["android", "ios"] + + print(f"存储配置:") + print(f" rclone_remote: {config_manager.storage_config.get('rclone_remote', 'remote')}") + print(f" storage_path: {config_manager.storage_path}") + print() + + print(f"CDN配置:") + print(f" base_url: {config_manager.cdn_base_url}") + print() + + for version in versions: + for platform in platforms: + # 获取rclone路径 + rclone_path = config_manager.get_rclone_path(version, platform) + + # 获取CDN URL + cdn_url = config_manager.get_cdn_url(version, platform) + + print(f"版本: {version}, 平台: {platform}") + print(f"Rclone路径: {rclone_path}") + print(f"CDN URL: {cdn_url}") + print() + + +if __name__ == "__main__": + main() diff --git a/examples/test_rclone_path.py b/examples/test_rclone_path.py new file mode 100644 index 0000000..31b1be4 --- /dev/null +++ b/examples/test_rclone_path.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +""" +测试rclone路径构建 + +验证rclone_remote配置项的正确使用。 +""" + +import sys +import os + +# 添加项目根目录到路径 +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) + +from config_man.core import ConfigManager + + +def main(): + """测试rclone路径构建""" + print("测试rclone路径构建") + print("=" * 50) + + # 创建配置管理器 + config_manager = ConfigManager() + + # 测试不同版本的路径构建 + versions = ["0.29", "0.30"] + platforms = ["android", "ios"] + + for version in versions: + for platform in platforms: + # 获取配置路径 + config_path = config_manager.get_config_path(version, platform) + + # 获取rclone路径 + rclone_path = config_manager.get_rclone_path(version, platform) + + print(f"\n版本: {version}, 平台: {platform}") + print(f"配置路径: {config_path}") + print(f"rclone路径: {rclone_path}") + + # 显示当前配置 + print(f"\n当前存储配置:") + print(f"存储路径: {config_manager.storage_path}") + print(f"rclone远程: {config_manager.storage_config.get('rclone_remote', 'remote')}") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/config_man/cli/main.py b/src/config_man/cli/main.py index 86c452f..e655ba2 100644 --- a/src/config_man/cli/main.py +++ b/src/config_man/cli/main.py @@ -2,6 +2,7 @@ import click import os from ..core.view_command import ViewCommand from ..utils.mock_rclone import patch_rclone +from ..utils.config import config @click.group() @@ -18,8 +19,8 @@ def cli(): @cli.command() @click.option('--version', required=True, help='版本号,格式为 0.29, 0.30, 0.31 等') @click.option('--json', 'json_format', is_flag=True, help='以JSON格式输出') -@click.option('--storage-path', default='ab', help='对象存储路径前缀') -@click.option('--cdn-url', default='', help='CDN基础URL') +@click.option('--storage-path', help='对象存储路径前缀(覆盖配置文件)') +@click.option('--cdn-url', help='CDN基础URL(覆盖配置文件)') @click.option('--mock', is_flag=True, help='使用模拟环境进行测试') def view(version, json_format, storage_path, cdn_url, mock): """查看配置文件 @@ -89,6 +90,67 @@ def force_update(platform, target_version, update_version): return 1 +@cli.command() +def show_config(): + """显示当前配置 + + 显示当前加载的配置信息。 + """ + import json + from rich.console import Console + from rich.panel import Panel + + console = Console() + + # 获取所有配置 + all_config = { + "storage": config.get_storage_config(), + "cdn": config.get_cdn_config(), + "crypto": config.get_crypto_config(), + "display": config.get_display_config(), + "logging": config.get_logging_config() + } + + # 格式化输出 + config_json = json.dumps(all_config, indent=2, ensure_ascii=False) + panel = Panel( + config_json, + title="当前配置", + border_style="blue" + ) + console.print(panel) + + # 显示配置文件路径 + console.print(f"\n配置文件路径: {config.config_file}") + + +@cli.command() +@click.option('--key', required=True, help='配置键,如 storage.path') +@click.option('--value', required=True, help='配置值') +def set_config(key, value): + """设置配置值 + + 设置指定的配置值并保存到配置文件。 + + 示例: + config-man set-config --key storage.path --value custom_path + config-man set-config --key cdn.base_url --value https://cdn.example.com + """ + try: + # 尝试转换数值类型 + if value.isdigit(): + value = int(value) + elif value.lower() in ('true', 'false'): + value = value.lower() == 'true' + + config.set(key, value) + config.save() + click.echo(f"✅ 配置已更新: {key} = {value}") + except Exception as e: + click.echo(f"❌ 设置配置失败: {e}") + return 1 + + def _check_rclone(): """检查rclone是否可用""" import subprocess diff --git a/src/config_man/core/config_manager.py b/src/config_man/core/config_manager.py index 89140b1..2cf522c 100644 --- a/src/config_man/core/config_manager.py +++ b/src/config_man/core/config_manager.py @@ -4,14 +4,18 @@ import subprocess import requests from typing import Dict, Optional, Tuple from ..utils.crypto import encrypt, decrypt +from ..utils.config import config class ConfigManager: """配置文件管理器""" - def __init__(self, storage_path: str = "ab", cdn_base_url: str = ""): - self.storage_path = storage_path - self.cdn_base_url = cdn_base_url + def __init__(self, storage_path: str = None, cdn_base_url: str = None): + # 使用配置文件中的值,如果参数为None + self.storage_path = storage_path or config.get_storage_path() + self.cdn_base_url = cdn_base_url or config.get_cdn_base_url() + self.storage_config = config.get_storage_config() + self.cdn_config = config.get_cdn_config() def version_to_path(self, version: str) -> str: """将版本号转换为存储路径格式""" @@ -24,45 +28,54 @@ class ConfigManager: filename = f"{platform}config.json" return f"{self.storage_path}/{version_path}/{filename}" + def get_rclone_path(self, version: str, platform: str) -> str: + """获取rclone完整路径(包含远程名称)""" + version_path = self.version_to_path(version) + filename = f"{platform}config.json" + rclone_remote = self.storage_config.get('rclone_remote', 'remote') + return f"{rclone_remote}:{self.storage_path}/{version_path}/{filename}" + def get_cdn_url(self, version: str, platform: str) -> str: """获取配置文件在CDN中的URL""" if not self.cdn_base_url: return "" version_path = self.version_to_path(version) filename = f"{platform}config.json" - return f"{self.cdn_base_url}/{self.storage_path}/{version_path}/{filename}" + return f"{self.cdn_base_url}/{version_path}/{filename}" def download_from_storage(self, version: str, platform: str) -> Optional[str]: """从对象存储下载并解密配置文件""" try: - config_path = self.get_config_path(version, platform) + # 获取rclone完整路径 + rclone_path = self.get_rclone_path(version, platform) # 使用rclone下载文件 + timeout = self.storage_config.get('timeout', 30) result = subprocess.run( - ["rclone", "cat", config_path], + ["rclone", "cat", rclone_path], capture_output=True, text=True, - timeout=30 + timeout=timeout ) if result.returncode != 0: - print(f"警告: 无法从对象存储下载 {config_path}: {result.stderr}") + print(f"警告: 无法从对象存储下载 {rclone_path}: {result.stderr}") return None # 解密文件内容 encrypted_content = result.stdout.strip() if not encrypted_content: - print(f"警告: 文件 {config_path} 为空") + print(f"警告: 文件 {rclone_path} 为空") return None decrypted_content = decrypt(encrypted_content) return decrypted_content except subprocess.TimeoutExpired: - print(f"错误: 下载 {config_path} 超时") + print(f"错误: 下载 {rclone_path} 超时") return None except Exception as e: - print(f"错误: 下载 {config_path} 失败: {str(e)}") + print(f"错误: 下载 {rclone_path} 失败: {str(e)}") return None def get_from_cdn(self, version: str, platform: str) -> Optional[str]: @@ -73,12 +86,25 @@ class ConfigManager: print(f"警告: CDN URL未配置,跳过CDN内容获取") return None - response = requests.get(cdn_url, timeout=10) + timeout = self.cdn_config.get('timeout', 10) + response = requests.get(cdn_url, timeout=timeout) response.raise_for_status() - # CDN中的内容应该是解密后的JSON - return response.text + # CDN中的内容可能是加密的,需要解密 + encrypted_content = response.text.strip() + if not encrypted_content: + print(f"警告: CDN返回空内容: {cdn_url}") + return None + # 尝试解密内容 + try: + decrypted_content = decrypt(encrypted_content) + return decrypted_content + except Exception as e: + # 如果解密失败,可能CDN中的内容已经是解密后的JSON + print(f"警告: CDN内容解密失败,尝试直接解析: {str(e)}") + return encrypted_content + except requests.RequestException as e: print(f"警告: 无法从CDN获取 {cdn_url}: {str(e)}") return None diff --git a/src/config_man/core/display_manager.py b/src/config_man/core/display_manager.py index 3a2671c..d86ffdf 100644 --- a/src/config_man/core/display_manager.py +++ b/src/config_man/core/display_manager.py @@ -4,6 +4,7 @@ from rich.table import Table from rich.panel import Panel from rich.text import Text import json +from ..utils.config import config class DisplayManager: @@ -11,6 +12,7 @@ class DisplayManager: def __init__(self): self.console = Console() + self.display_config = config.get_display_config() def display_config_comparison(self, version: str, platform: str, storage_config: Dict, cdn_config: Dict, @@ -29,16 +31,17 @@ class DisplayManager: table.add_column("状态", style="red", width=10) # 添加数据行 + truncate_length = self.display_config.get('truncate_length', 25) for key, data in differences.items(): storage_value = str(data["storage"]) cdn_value = str(data["cdn"]) status = "❌ 不同" if data["different"] else "✅ 一致" # 截断过长的值 - if len(storage_value) > 25: - storage_value = storage_value[:22] + "..." - if len(cdn_value) > 25: - cdn_value = cdn_value[:22] + "..." + if len(storage_value) > truncate_length: + storage_value = storage_value[:truncate_length-3] + "..." + if len(cdn_value) > truncate_length: + cdn_value = cdn_value[:truncate_length-3] + "..." table.add_row(key, storage_value, cdn_value, status) diff --git a/src/config_man/utils/__init__.py b/src/config_man/utils/__init__.py index 162f065..f131de0 100644 --- a/src/config_man/utils/__init__.py +++ b/src/config_man/utils/__init__.py @@ -1,10 +1,11 @@ """ 工具模块 -包含加密解密、模拟环境等工具功能。 +包含加密解密、模拟环境、配置管理等工具功能。 """ from .crypto import encrypt, decrypt from .mock_rclone import MockRclone, patch_rclone +from .config import config, Config -__all__ = ["encrypt", "decrypt", "MockRclone", "patch_rclone"] \ No newline at end of file +__all__ = ["encrypt", "decrypt", "MockRclone", "patch_rclone", "config", "Config"] \ No newline at end of file diff --git a/src/config_man/utils/config.py b/src/config_man/utils/config.py new file mode 100644 index 0000000..4a9e3ae --- /dev/null +++ b/src/config_man/utils/config.py @@ -0,0 +1,222 @@ +""" +配置管理模块 + +支持从配置文件和环境变量读取配置信息。 +""" + +import os +import json +from pathlib import Path +from typing import Dict, Any, Optional + + +class Config: + """配置管理类""" + + def __init__(self, config_file: Optional[str] = None): + """ + 初始化配置管理器 + + Args: + config_file: 配置文件路径,如果为None则使用默认路径 + """ + self.config_file = config_file or self._get_default_config_path() + self._config = self._load_config() + + def _get_default_config_path(self) -> str: + """获取默认配置文件路径""" + # 优先使用用户主目录下的配置文件 + home_config = Path.home() / ".config" / "config-man" / "config-man.json" + if home_config.exists(): + return str(home_config) + + # 其次使用项目根目录下的配置文件 + project_config = Path(__file__).parent.parent.parent.parent / "config-man.json" + if project_config.exists(): + return str(project_config) + + # 最后使用默认配置文件 + return str(Path(__file__).parent.parent.parent.parent / "config-man.json") + + def _load_config(self) -> Dict[str, Any]: + """加载配置文件""" + default_config = { + "storage": { + "path": "ab", + "rclone_remote": "remote", + "timeout": 30 + }, + "cdn": { + "base_url": "", + "timeout": 10, + "retry_count": 3 + }, + "crypto": { + "algorithm": "DES", + "key": "tbambooz" + }, + "logging": { + "level": "INFO", + "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s" + }, + "display": { + "table_format": "grid", + "max_width": 80, + "truncate_length": 25 + } + } + + # 如果配置文件存在,则加载并合并 + if os.path.exists(self.config_file): + try: + with open(self.config_file, 'r', encoding='utf-8') as f: + file_config = json.load(f) + # 深度合并配置 + self._merge_config(default_config, file_config) + except (json.JSONDecodeError, IOError) as e: + print(f"警告: 无法加载配置文件 {self.config_file}: {e}") + + # 从环境变量覆盖配置 + self._load_from_env(default_config) + + return default_config + + def _merge_config(self, base: Dict[str, Any], override: Dict[str, Any]) -> None: + """深度合并配置""" + for key, value in override.items(): + if key in base and isinstance(base[key], dict) and isinstance(value, dict): + self._merge_config(base[key], value) + else: + base[key] = value + + def _load_from_env(self, config: Dict[str, Any]) -> None: + """从环境变量加载配置""" + env_mappings = { + "CONFIG_MAN_STORAGE_PATH": ("storage", "path"), + "CONFIG_MAN_STORAGE_RCLONE_REMOTE": ("storage", "rclone_remote"), + "CONFIG_MAN_STORAGE_TIMEOUT": ("storage", "timeout"), + "CONFIG_MAN_CDN_BASE_URL": ("cdn", "base_url"), + "CONFIG_MAN_CDN_TIMEOUT": ("cdn", "timeout"), + "CONFIG_MAN_CDN_RETRY_COUNT": ("cdn", "retry_count"), + "CONFIG_MAN_CRYPTO_KEY": ("crypto", "key"), + "CONFIG_MAN_CRYPTO_ALGORITHM": ("crypto", "algorithm"), + "CONFIG_MAN_LOG_LEVEL": ("logging", "level"), + "CONFIG_MAN_DISPLAY_TABLE_FORMAT": ("display", "table_format"), + "CONFIG_MAN_DISPLAY_MAX_WIDTH": ("display", "max_width"), + "CONFIG_MAN_DISPLAY_TRUNCATE_LENGTH": ("display", "truncate_length"), + } + + for env_var, config_path in env_mappings.items(): + env_value = os.getenv(env_var) + if env_value is not None: + # 获取配置路径的父级 + current = config + for key in config_path[:-1]: + if key not in current: + current[key] = {} + current = current[key] + + # 设置值,尝试转换为适当类型 + key = config_path[-1] + try: + # 尝试转换为整数 + if isinstance(current.get(key), int): + current[key] = int(env_value) + # 尝试转换为布尔值 + elif isinstance(current.get(key), bool): + current[key] = env_value.lower() in ('true', '1', 'yes', 'on') + else: + current[key] = env_value + except ValueError: + current[key] = env_value + + def get(self, key: str, default: Any = None) -> Any: + """ + 获取配置值 + + Args: + key: 配置键,支持点号分隔的嵌套键,如 'storage.path' + default: 默认值 + + Returns: + 配置值 + """ + keys = key.split('.') + value = self._config + + for k in keys: + if isinstance(value, dict) and k in value: + value = value[k] + else: + return default + + return value + + def set(self, key: str, value: Any) -> None: + """ + 设置配置值 + + Args: + key: 配置键,支持点号分隔的嵌套键 + value: 配置值 + """ + keys = key.split('.') + current = self._config + + # 导航到父级 + for k in keys[:-1]: + if k not in current: + current[k] = {} + current = current[k] + + # 设置值 + current[keys[-1]] = value + + def save(self) -> None: + """保存配置到文件""" + try: + # 确保配置目录存在 + config_dir = os.path.dirname(self.config_file) + if config_dir: + os.makedirs(config_dir, exist_ok=True) + + with open(self.config_file, 'w', encoding='utf-8') as f: + json.dump(self._config, f, indent=2, ensure_ascii=False) + except IOError as e: + print(f"警告: 无法保存配置文件 {self.config_file}: {e}") + + def get_storage_path(self) -> str: + """获取存储路径""" + return self.get('storage.path', 'ab') + + def get_cdn_base_url(self) -> str: + """获取CDN基础URL""" + return self.get('cdn.base_url', '') + + def get_crypto_key(self) -> str: + """获取加密密钥""" + return self.get('crypto.key', 'tbambooz') + + def get_display_config(self) -> Dict[str, Any]: + """获取显示配置""" + return self.get('display', {}) + + def get_storage_config(self) -> Dict[str, Any]: + """获取存储配置""" + return self.get('storage', {}) + + def get_cdn_config(self) -> Dict[str, Any]: + """获取CDN配置""" + return self.get('cdn', {}) + + def get_crypto_config(self) -> Dict[str, Any]: + """获取加密配置""" + return self.get('crypto', {}) + + def get_logging_config(self) -> Dict[str, Any]: + """获取日志配置""" + return self.get('logging', {}) + + +# 全局配置实例 +config = Config() \ No newline at end of file diff --git a/src/config_man/utils/mock_rclone.py b/src/config_man/utils/mock_rclone.py index e6ae8aa..e24815c 100644 --- a/src/config_man/utils/mock_rclone.py +++ b/src/config_man/utils/mock_rclone.py @@ -21,6 +21,8 @@ class MockRclone: def _create_test_configs(self): """创建测试配置文件""" + from ..utils.config import config + test_configs = { "0.29": { "android": { @@ -82,17 +84,20 @@ class MockRclone: } } + # 获取配置的存储路径 + storage_path = config.get_storage_path() + # 创建测试目录结构 for version, platforms in test_configs.items(): - version_path = f"{self.test_data_path}/ab/{version.replace('.', '_')}" + version_path = f"{self.test_data_path}/{storage_path}/{version.replace('.', '_')}" os.makedirs(version_path, exist_ok=True) - for platform, config in platforms.items(): + for platform, config_data in platforms.items(): filename = f"{platform}config.json" filepath = os.path.join(version_path, filename) # 加密配置并保存 - config_json = json.dumps(config, indent=2, ensure_ascii=False) + config_json = json.dumps(config_data, indent=2, ensure_ascii=False) encrypted_config = encrypt(config_json) with open(filepath, 'w', encoding='utf-8') as f: @@ -102,8 +107,16 @@ class MockRclone: def mock_rclone_cat(self, path: str) -> str: """模拟rclone cat命令""" + # 处理rclone路径格式:remote:path 或 path + if ':' in path: + # 如果包含远程名称,提取路径部分 + _, actual_path = path.split(':', 1) + else: + # 如果没有远程名称,直接使用路径 + actual_path = path + # 将路径转换为本地文件路径 - local_path = os.path.join(self.test_data_path, path) + local_path = os.path.join(self.test_data_path, actual_path) if not os.path.exists(local_path): raise FileNotFoundError(f"文件不存在: {path}")