172 lines
5.2 KiB
C++
172 lines
5.2 KiB
C++
#include <CLI11.hpp>
|
|
#include <chrono>
|
|
#include <cstdlib>
|
|
#include <filesystem>
|
|
#include <fmt/core.h>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <thread>
|
|
#include <vector>
|
|
#include <zoost/zoost.h>
|
|
|
|
using namespace zoost;
|
|
namespace fs = std::filesystem;
|
|
|
|
struct Args {
|
|
std::string downloader;
|
|
std::string url;
|
|
std::string output;
|
|
std::string proxy = "http://127.0.0.1:7897";
|
|
uint32_t retry = 2;
|
|
uint64_t retry_interval = 5;
|
|
};
|
|
|
|
// 执行命令并返回退出码
|
|
int execute_command(const std::string& program, const std::vector<std::string>& args)
|
|
{
|
|
try {
|
|
// 构建命令字符串用于显示
|
|
std::string cmd_str = program;
|
|
for (const auto& arg : args) {
|
|
cmd_str += " " + arg;
|
|
}
|
|
fmt::print("执行命令: {}\n", cmd_str);
|
|
|
|
// 构建命令行参数数组
|
|
std::vector<const char*> argv;
|
|
argv.push_back(program.c_str());
|
|
|
|
for (const auto& arg : args) {
|
|
argv.push_back(arg.c_str());
|
|
}
|
|
argv.push_back(nullptr);
|
|
|
|
// 执行命令
|
|
int result = std::system(cmd_str.c_str());
|
|
|
|
// Windows 上直接返回,Linux/macOS 需要使用宏提取退出码
|
|
#ifdef _WIN32
|
|
return result;
|
|
#else
|
|
return WEXITSTATUS(result);
|
|
#endif
|
|
} catch (const std::exception& e) {
|
|
fmt::print(stderr, "执行命令失败: {}\n", e.what());
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// 调用外部下载器
|
|
int call_downloader(const std::string& downloader_path, const std::string& url, const std::string& output_dir,
|
|
const std::string& proxy, bool resume)
|
|
{
|
|
|
|
std::vector<std::string> args = {"dl", "-u", url, "--proxy", proxy, "-d", output_dir, "--skip-same"};
|
|
|
|
if (resume) {
|
|
args.push_back("--continue");
|
|
}
|
|
|
|
return execute_command(downloader_path, args);
|
|
}
|
|
|
|
// 带重试的下载函数
|
|
int download_with_retry(const Args& args)
|
|
{
|
|
uint32_t max_attempts = args.retry + 1;
|
|
uint32_t attempt = 1;
|
|
|
|
while (true) {
|
|
fmt::print("\n=== 尝试 {}/{} ===\n", attempt, max_attempts);
|
|
|
|
int status = call_downloader(args.downloader, args.url, args.output, args.proxy, false);
|
|
|
|
if (status == 0) { // 成功
|
|
fmt::print("✅ 第{}次尝试成功\n", attempt);
|
|
return status;
|
|
} else if (attempt < max_attempts) { // 失败但还可重试
|
|
fmt::print("❌ 第{}次尝试失败,{}秒后重试...\n", attempt, args.retry_interval);
|
|
|
|
std::this_thread::sleep_for(std::chrono::seconds(args.retry_interval));
|
|
attempt++;
|
|
} else { // 达到最大重试次数
|
|
fmt::print(stderr, "❌ 已达到最大重试次数({}次),下载失败\n", max_attempts);
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
|
|
int main(int argc, char** argv)
|
|
{
|
|
Common::SetOutputU8();
|
|
|
|
Args args;
|
|
|
|
CLI::App app{"简单的下载器包装工具"};
|
|
|
|
// 添加命令行参数
|
|
app.add_option("-d,--downloader", args.downloader, "外部下载器程序的路径")->required()->check(CLI::ExistingFile);
|
|
|
|
app.add_option("-u,--url", args.url, "远程文件的URL")->required();
|
|
|
|
app.add_option("-o,--output", args.output, "本地存储目录")->required()->check(CLI::ExistingDirectory);
|
|
|
|
// proxy 参数(有默认值的字符串参数)
|
|
app.add_option("-p,--proxy", args.proxy, "代理")->default_val("http://127.0.0.1:7897");
|
|
|
|
// retry 参数(有默认值的数值参数)
|
|
app.add_option("-r,--retry", args.retry, "重试次数")->default_val(2)->check(CLI::NonNegativeNumber);
|
|
|
|
// retry-interval 参数(有默认值的数值参数)
|
|
app.add_option("-i,--retry-interval", args.retry_interval, "重试间隔(秒)")->default_val(5)->check(CLI::NonNegativeNumber);
|
|
|
|
try {
|
|
app.parse(argc, argv);
|
|
} catch (const CLI::ParseError& e) {
|
|
return app.exit(e);
|
|
}
|
|
|
|
// 打印URL(无论成功失败都会在最后打印)
|
|
fmt::print("下载URL: {}\n", args.url);
|
|
|
|
// CLI11 已经做了基本的验证,但我们可以再次验证
|
|
if (!fs::exists(args.downloader)) {
|
|
fmt::print(stderr, "错误: 下载器文件不存在: {}\n", args.downloader);
|
|
return 1;
|
|
}
|
|
|
|
if (!fs::exists(args.output)) {
|
|
fmt::print(stderr, "错误: 输出目录不存在: {}\n", args.output);
|
|
return 1;
|
|
}
|
|
|
|
if (!fs::is_directory(args.output)) {
|
|
fmt::print(stderr, "错误: 输出路径不是目录: {}\n", args.output);
|
|
return 1;
|
|
}
|
|
|
|
fmt::print("开始下载...\n");
|
|
fmt::print(" 下载器: {}\n", args.downloader);
|
|
fmt::print(" 远程URL: {}\n", args.url);
|
|
fmt::print(" 输出目录: {}\n", args.output);
|
|
fmt::print(" 代理: {}\n", args.proxy);
|
|
fmt::print(" 重试次数: {}\n", args.retry);
|
|
fmt::print(" 重试间隔: {}秒\n", args.retry_interval);
|
|
|
|
// 调用外部下载器,带有重试逻辑
|
|
int status = download_with_retry(args);
|
|
|
|
// 无论成功失败,都打印URL
|
|
fmt::print("\n下载URL: {}\n", args.url);
|
|
|
|
// 判断下载是否成功
|
|
if (status == 0) {
|
|
fmt::print("✅ 下载成功!\n");
|
|
return 0;
|
|
} else {
|
|
fmt::print(stderr, "❌ 下载失败,退出码: {}\n", status);
|
|
return 1;
|
|
}
|
|
} |