chsrc/src/framework/chsrc-framework.c
2025-03-07 11:06:56 +08:00

1280 lines
34 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/** ------------------------------------------------------------
* SPDX-License-Identifier: GPL-3.0-or-later
* -------------------------------------------------------------
* File Name : chsrc-framework.c
* File Authors : Aoran Zeng <ccmywish@qq.com>
* | Heng Guo <2085471348@qq.com>
* Contributors : Peng Gao <gn3po4g@outlook.com>
* | Happy Game <happygame10124@gmail.com>
* | Yangmoooo <yangmoooo@outlook.com>
* |
* Created On : <2023-08-29>
* Last Modified : <2025-03-07>
*
* chsrc framework
* ------------------------------------------------------------*/
#include "chsrc-framework.h"
#include "mirror.c"
bool ProgMode_CMD_Measure = false;
bool ProgMode_CMD_Reset = false;
bool ProgMode_Target_Group = false;
int ProgMode_Leader_Selected_Index = -1;
bool ProgMode_Run_as_a_Service = false;
enum ChgType_t ProgMode_ChgType = ChgType_Auto;
bool CliOpt_IPv6 = false;
bool CliOpt_Locally = false;
bool CliOpt_InEnglish = false;
bool CliOpt_DryRun = false;
bool CliOpt_NoColor = false;
void
chsrc_note2 (const char *str)
{
char *msg = CliOpt_InEnglish ? "NOTE" : "提示";
xy_log_brkt (yellow(App_Name), bdyellow(msg), yellow(str));
}
static void
log_write_op (const char *filename)
{
char *msg = CliOpt_InEnglish ? "WRITE" : "写入";
xy_log_brkt (blue(App_Name), bdblue(msg), blue(filename));
}
static void
log_backup_op (const char *filename)
{
char *msg = CliOpt_InEnglish ? "BACKUP" : "备份";
char *bak = xy_2strjoin (filename, ".bak");
xy_log_brkt (blue(App_Name), bdblue(msg), xy_strjoin (3, bdyellow(filename), " -> ", bdgreen(bak)));
}
static void
log_check_result (const char *check_what, const char *check_type, bool exist)
{
char *chk_msg = NULL;
char *not_exist_msg = NULL;
char *exist_msg = NULL;
if (CliOpt_InEnglish)
{
chk_msg = "CHECK";
not_exist_msg = " doesn't exist";
exist_msg = " exists";
}
else
{
chk_msg = "检查";
not_exist_msg = " 不存在";
exist_msg = " 存在";
}
if (!exist)
{
xy_log_brkt (App_Name, bdred (chk_msg), xy_strjoin (5,
red (NoMark " "), check_type, " ", red (check_what), not_exist_msg));
}
else
{
xy_log_brkt (App_Name, bdgreen (chk_msg), xy_strjoin (5,
green (YesMark " "), check_type, " ", green (check_what), exist_msg));
}
}
static void
log_cmd_result (bool result, int exit_status)
{
char *run_msg = NULL;
char *succ_msg = NULL;
char *fail_msg = NULL;
if (CliOpt_InEnglish)
{
run_msg = "RUN";
succ_msg = YesMark " executed successfully";
fail_msg = NoMark " executed unsuccessfully, exit status: ";
}
else
{
run_msg = "运行";
succ_msg = YesMark " 命令执行成功";
fail_msg = NoMark " 命令执行失败,退出状态: ";
}
if (result)
xy_log_brkt (green (App_Name), bdgreen (run_msg), green (succ_msg));
else
{
char buf[8] = {0};
sprintf (buf, "%d", exit_status);
char *log = xy_2strjoin (red (fail_msg), bdred (buf));
xy_log_brkt (red (App_Name), bdred (run_msg), log);
}
}
bool
is_url (const char *str)
{
return (xy_str_start_with (str, "http://") || xy_str_start_with (str, "https://"));
}
/**
* 检测二进制程序是否存在
*
* @param prog_name 要检测的二进制程序名
*
* @param check_cmd 检测 `prog_name` 是否存在的一段命令,一般来说,填 `prog_name` 本身即可,
* 但是某些情况下,需要使用其他命令绕过一些特殊情况,比如 python 这个命令在Windows上
* 会自动打开 Microsoft Store需避免
*/
bool
chsrc_query_program_exist (char *prog_name, char *check_cmd, int mode)
{
char *which = check_cmd;
int status = system (which);
// char buf[32] = {0}; sprintf(buf, "错误码: %d", status);
char *msg = CliOpt_InEnglish ? "command" : "命令";
if (0 != status)
{
if (mode & Noisy_When_NonExist)
{
// xy_warn (xy_strjoin(4, "× 命令 ", progname, " 不存在,", buf));
log_check_result (prog_name, msg, false);
}
return false;
}
else
{
if (mode & Noisy_When_Exist)
log_check_result (prog_name, msg, true);
return true;
}
}
/**
* @note
* 1. 一般只在 Recipe 中使用,显式检测每一个需要用到的 program
* 2. 无论存在与否,**均输出**
*
*/
bool
chsrc_check_program (char *prog_name)
{
char *quiet_cmd = xy_str_to_quietcmd (xy_2strjoin (prog_name, " --version"));
return chsrc_query_program_exist (prog_name, quiet_cmd, Noisy_When_Exist|Noisy_When_NonExist);
}
/**
* @note
* 1. 此函数没有强制性,只返回检查结果
* 2. 无论存在与否,**均不输出**
* 3. 此函数只能对接受 --version 选项的命令行程序有效
*
*/
bool
chsrc_check_program_quietly (char *prog_name)
{
char *quiet_cmd = xy_str_to_quietcmd (xy_2strjoin (prog_name, " --version"));
return chsrc_query_program_exist (prog_name, quiet_cmd, Quiet_When_Exist|Quiet_When_NonExist);
}
/**
* @note 存在时不输出,不存在时才输出
*
*/
bool
chsrc_check_program_quietly_when_exist (char *prog_name)
{
char *quiet_cmd = xy_str_to_quietcmd (xy_2strjoin (prog_name, " --version"));
return chsrc_query_program_exist (prog_name, quiet_cmd, Quiet_When_Exist|Noisy_When_NonExist);
}
/**
* @note
* 1. 此函数具有强制性,检测不到就直接退出
* 2. 检查到存在时不输出,检查到不存在时输出
*
*/
void
chsrc_ensure_program (char *prog_name)
{
char *quiet_cmd = xy_str_to_quietcmd (xy_2strjoin (prog_name, " --version"));
bool exist = chsrc_query_program_exist (prog_name, quiet_cmd, Quiet_When_Exist|Noisy_When_NonExist);
if (exist)
{
// OK, nothing should be done
}
else
{
char *msg1 = CliOpt_InEnglish ? "not found " : "未找到 ";
char *msg2 = CliOpt_InEnglish ? " command, please check for existence" : " 命令,请检查是否存在";
chsrc_error (xy_strjoin (3, msg1, prog_name, msg2));
exit (Exit_UserCause);
}
}
bool
chsrc_check_file (char *path)
{
char *msg = CliOpt_InEnglish ? "file" : "文件";
if (xy_file_exist (path))
{
log_check_result (path, msg, true);
return true;
}
else
{
log_check_result (path, msg, false);
return false;
}
}
/**
* 用于 _setsrc 函数检测用户输入的镜像站code是否存在于该target可用源中
*
* @note 一个源Source必定来自于一个Provider所以该函数名叫 query_provider_exist
*
* @param target 目标名
* @param input 如果用户输入 default 或者 def则选择第一个源
*/
int
query_provider_exist (Source_t *sources, size_t size, char *target, char *input)
{
if (is_url (input))
{
char *msg = CliOpt_InEnglish ? "Using user-defined sources for this software is not supported at this time, please contact the developers to ask why or request support" : "暂不支持对该软件使用用户自定义源,请联系开发者询问原因或请求支持";
chsrc_error (msg);
exit (Exit_Unsupported);
}
if (0==size)
{
char *msg1 = CliOpt_InEnglish ? "Currently " : "当前 ";
char *msg2 = CliOpt_InEnglish ? " doesn't have any source available. Please contact the maintainers" : " 无任何可用源,请联系维护者";
chsrc_error (xy_strjoin (3, msg1, target, msg2));
exit (Exit_MaintainerCause);
}
if (1==size)
{
char *msg1 = CliOpt_InEnglish ? "Currently " : "当前 ";
char *msg2 = CliOpt_InEnglish ? " only the upstream source exists. Please contact the maintainers" : " 仅存在上游默认源,请联系维护者";
chsrc_error (xy_strjoin (3, msg1, target, msg2));
exit (Exit_MaintainerCause);
}
/* if (xy_streql ("reset", input)) 不再使用这种方式 */
if (ProgMode_CMD_Reset)
{
char *msg = CliOpt_InEnglish ? "Will reset to the upstream's default source" : "将重置为上游默认源";
say (msg);
return 0; /* 返回第1个因为第1个是上游默认源 */
}
if (2==size)
{
char *msg1 = CliOpt_InEnglish ? " is " : "";
char *msg2 = CliOpt_InEnglish ? "'s ONLY mirror available currently, thanks for their generous support"
: " 目前唯一可用镜像站,感谢他们的慷慨支持";
const char *name = CliOpt_InEnglish ? sources[1].mirror->abbr
: sources[1].mirror->name;
chsrc_succ (xy_strjoin (4, name, msg1, target, msg2));
}
if (xy_streql ("first", input))
{
char *msg = CliOpt_InEnglish ? "Will use the first speedy source measured by maintainers" : "将使用维护团队测速第一的源";
say (msg);
return 1; /* 返回第2个因为第1个是上游默认源 */
}
int idx = 0;
Source_t src = sources[0];
bool exist = false;
for (int i=0; i<size; i++)
{
src = sources[i];
if (xy_streql (src.mirror->code, input))
{
idx = i;
exist = true;
break;
}
}
if (!exist)
{
{
char *msg1 = CliOpt_InEnglish ? "Mirror site " : "镜像站 ";
char *msg2 = CliOpt_InEnglish ? " doesn't exist" : " 不存在";
chsrc_error (xy_strjoin (3, msg1, input, msg2));
}
char *msg = CliOpt_InEnglish ? "To see available sources, use chsrc list " : "查看可使用源,请使用 chsrc list ";
chsrc_error (xy_2strjoin (msg, target));
exit (Exit_UserCause);
}
return idx;
}
/**
* 该函数来自 oh-my-mirrorz.py由 @ccmywish 翻译为C语言但功劳和版权属于原作者
*
* @param speed 单位为Byte/s
*/
static char *
to_human_readable_speed (double speed)
{
char *scale[] = {"Byte/s", "KByte/s", "MByte/s", "GByte/s", "TByte/s"};
int i = 0;
while (speed > 1024.0)
{
i += 1;
speed /= 1024.0;
}
char *buf = xy_malloc0 (64);
sprintf (buf, "%.2f %s", speed, scale[i]);
char *new = NULL;
if (i <= 1 ) new = red (buf);
else
{
if (i == 2 && speed < 2.00) new = yellow (buf);
else new = green (buf);
}
return new;
}
/**
* 测速代码参考自 https://github.com/mirrorz-org/oh-my-mirrorz/blob/master/oh-my-mirrorz.py
* 功劳和版权属于原作者,由 @ccmywish 修改为C语言并做了额外调整
*
* @return 返回测得的速度,若出错,返回-1
*
* 该函数实际原型为 char * (*)(const char*)
*/
static void *
measure_speed_for_url (void *url)
{
char *time_sec = NULL;
time_sec = "8";
/**
* 现在我们切换至跳转后的链接来测速,不再使用下述判断
*
* if (xy_str_start_with(url, "https://registry.npmmirror"))
* {
* // 这里 npmmirror 跳转非常慢需要1~3秒所以我们给它留够至少8秒测速时间否则非常不准
* time_sec = "10";
* }
*/
char *ipv6 = ""; // 默认不启用
if (CliOpt_IPv6==true)
{
ipv6 = "--ipv6";
}
char *os_devnull = xy_os_devnull;
/**
* @note 我们用 —L因为部分链接会跳转到其他地方比如: RubyChina, npmmirror
*/
char *curl_cmd = xy_strjoin (8, "curl -qsL ", ipv6,
" -o ", os_devnull,
" -w \"%{http_code} %{speed_download}\" -m", time_sec,
" -A chsrc/" Chsrc_Version " ", url);
// chsrc_info (xy_2strjoin ("测速命令 ", curl_cmd));
char *curl_buf = xy_run (curl_cmd, 0);
return curl_buf;
}
/**
* @return 返回速度speed单位为 Byte/s
*/
static double
parse_and_say_curl_result (char *curl_buf)
{
// 分隔两部分数据
char *split = strchr (curl_buf, ' ');
if (split) *split = '\0';
// say(curl_buf); say(split+1);
int http_code = atoi (curl_buf);
double speed = atof (split+1);
char *speedstr = to_human_readable_speed (speed);
if (200!=http_code)
{
char *http_code_str = yellow (xy_2strjoin ("HTTP码 ", curl_buf));
say (xy_strjoin (3, speedstr, " | ", http_code_str));
}
else
{
say (speedstr);
}
return speed;
}
static int
get_max_ele_idx_in_dbl_ary (double *array, int size)
{
double maxval = array[0];
int maxidx = 0;
for (int i=1; i<size; i++)
{
if (array[i]>maxval)
{
maxval = array[i];
maxidx = i;
}
}
return maxidx;
}
/**
* @param sources 所有待测源
* @param size 待测源的数量
* @param[out] speed_records 速度值记录单位为Byte/s
*/
static void
measure_speed_for_every_source (Source_t sources[], int size, double speed_records[])
{
// bool get_measured[size]; /* 是否真正执行了测速 */
int get_measured_n = 0; /* 测速了几个 */
char *measure_msgs[size];
double speed = 0.0;
for (int i=0; i<size; i++)
{
Source_t src = sources[i];
SourceProvider_t *provider = src.provider;
SpeedMeasureInfo_t smi = provider->smi;
bool skip = smi.skip;
const char *url = smi.url;
if (!skip && NULL==url)
// 这种情况应当被视为bug但是我们目前还是软处理
{
char *msg1 = CliOpt_InEnglish ? "Maintainers don't offer " : "维护者未提供 ";
char *msg2 = CliOpt_InEnglish ? " mirror site's speed measure link, so skip it" : " 镜像站测速链接,跳过该站点";
chsrc_warn (xy_strjoin (3, msg1, provider->code, msg2));
speed = 0;
speed_records[i] = speed;
// get_measured[i] = false;
measure_msgs[i] = NULL;
}
if (skip)
{
if (xy_streql ("upstream", provider->code))
{
/* 上游源不测速但不置0因为要避免这种情况: 可能其他镜像站测速都为0最后反而选择了该 upstream */
speed = -1024*1024*1024;
if (!src.url)
{
smi.skip_reason_CN = "上游默认源URL未知请帮助补充";
smi.skip_reason_EN = "The default upstream source URL is unknown, please help to add";
}
}
else if (xy_streql ("user", provider->code))
{
/* 代码不会执行至此 */
speed = 1024*1024*1024;
}
else
{
/* 不测速的 Provider */
speed = 0;
}
// get_measured[i] = false;
speed_records[i] = speed;
const char *msg = CliOpt_InEnglish ? provider->abbr : provider->name;
const char *skip_reason = CliOpt_InEnglish ? smi.skip_reason_EN : smi.skip_reason_CN;
if (NULL==skip_reason)
{
skip_reason = CliOpt_InEnglish ? "SKIP for no reason" : "无理由跳过";
}
measure_msgs[i] = xy_strjoin (4, " x ", msg, " ", yellow(skip_reason));
printf ("%s\n", measure_msgs[i]);
}
else
{
const char *msg = CliOpt_InEnglish ? provider->abbr : provider->name;
if (xy_streql ("upstream", provider->code))
{
measure_msgs[i] = xy_strjoin (5, " ^ ", msg, " (", src.url, ") ... ");
}
else
{
measure_msgs[i] = xy_strjoin (3, " - ", msg, " ... ");
}
printf ("%s", measure_msgs[i]);
fflush (stdout);
char *url_ = xy_strdup (url);
char *curl_result = measure_speed_for_url (url_);
double speed = parse_and_say_curl_result (curl_result);
speed_records[i] = speed;
}
}
}
/**
* 自动测速选择镜像站和源
*/
int
select_mirror_autoly (Source_t *sources, size_t size, const char *target_name)
{
/* reset 时选择默认源 */
if (ProgMode_CMD_Reset)
return 0;
if (!CliOpt_DryRun)
{
char *msg = CliOpt_InEnglish ? "Measuring speed in sequence" : "测速中";
xy_log_brkt (App_Name, bdpurple (CliOpt_InEnglish ? "MEASURE" : "测速"), msg);
br();
}
if (0==size || 1==size)
{
char *msg1 = CliOpt_InEnglish ? "Currently " : "当前 ";
char *msg2 = CliOpt_InEnglish ? "No any source, please contact maintainers: chsrc issue" : " 无任何可用源,请联系维护者: chsrc issue";
chsrc_error (xy_strjoin (3, msg1, target_name, msg2));
exit (Exit_MaintainerCause);
}
if (CliOpt_DryRun)
/* Dry Run 时,跳过测速 */
{
return 1; /* 原则第一个源 */
}
bool only_one = false;
if (2==size) only_one = true;
/** --------------------------------------------- */
bool exist_curl = chsrc_check_program_quietly_when_exist ("curl");
if (!exist_curl)
{
char *msg = CliOpt_InEnglish ? "No curl, unable to measure speed" : "没有curl命令无法测速";
chsrc_error (msg);
exit (Exit_UserCause);
}
if (xy_on_windows)
{
char *curl_version = xy_run ("curl --version", 1);
/**
* https://github.com/RubyMetric/chsrc/issues/144
*
* Cygwin上curl 的版本信息为:
*
* curl 8.9.1 (x86_64-pc-cygwin)
*
*/
if (strstr (curl_version, "pc-cygwin"))
{
char *msg = CliOpt_InEnglish ? "You're using curl built by Cygwin which has a bug! Please use another curl!" : "你使用的是Cygwin构建的curl该版本的curl存在bug请改用其他版本的curl";
chsrc_error (msg);
exit (Exit_UserCause);
}
}
/** --------------------------------------------- */
/* 总测速记录值 */
double speed_records[size];
measure_speed_for_every_source (sources, size, speed_records);
br();
/* DEBUG */
/*
for (int i=0; i<size; i++)
{
printf ("speed_records[%d] = %f\n", i, speed_records[i]);
}
*/
int fast_idx = get_max_ele_idx_in_dbl_ary (speed_records, size);
if (only_one)
{
char *msg1 = CliOpt_InEnglish ? "NOTICE mirror site: " : "镜像站提示: ";
char *is = CliOpt_InEnglish ? " is " : "";
char *msg2 = CliOpt_InEnglish ? "'s ONLY mirror available currently, thanks for their generous support"
: " 目前唯一可用镜像站,感谢他们的慷慨支持";
const char *name = CliOpt_InEnglish ? sources[fast_idx].mirror->abbr
: sources[fast_idx].mirror->name;
say (xy_strjoin (5, msg1, bdgreen(name), green(is), green(target_name), green(msg2)));
}
else
{
char *msg = CliOpt_InEnglish ? "FASTEST mirror site: " : "最快镜像站: ";
const char *name = CliOpt_InEnglish ? sources[fast_idx].mirror->abbr
: sources[fast_idx].mirror->name;
say (xy_2strjoin (msg, green(name)));
}
// https://github.com/RubyMetric/chsrc/pull/71
if (ProgMode_CMD_Measure)
{
char *msg = CliOpt_InEnglish ? "URL of above source: " : "镜像源地址: ";
say (xy_2strjoin (msg, green(sources[fast_idx].url)));
}
return fast_idx;
}
static bool
source_is_upstream (Source_t *source)
{
return xy_streql (source->mirror->code, "upstream");
}
static bool
source_is_userdefine (Source_t *source)
{
return xy_streql (source->mirror->code, "user");
}
static bool
source_has_empty_url (Source_t *source)
{
return source->url == NULL;
}
static bool
is_reset_mode ()
{
return ProgMode_CMD_Reset;
}
/**
* 用于 _setsrc 函数
*
* 1. 告知用户选择了什么源和镜像
* 2. 对选择的源和镜像站进行一定的校验
*
*/
void
chsrc_confirm_source (Source_t *source)
{
// 由于实现问题,我们把本应该独立出去的上游默认源,也放在了可以换源的数组中,而且放在第一个
// chsrc 已经规避用户使用未实现的 `chsrc reset`
// 但是某些用户可能摸索着强行使用 chsrc set target upstream从而执行起该禁用的功能
// 之所以禁用,是因为有的 reset 我们并没有实现,我们在这里阻止这些邪恶的用户
if (source_is_upstream (source) && source_has_empty_url (source))
{
char *msg = CliOpt_InEnglish ? "Not implement `reset` for the target yet" : "暂未对该目标实现重置";
chsrc_error (msg);
exit (Exit_Unsupported);
}
else if (source_has_empty_url (source))
{
char *msg = CliOpt_InEnglish ? "URL of the source doesn't exist, please report a bug to the dev team" : \
"该源URL不存在请向维护团队提交bug";
chsrc_error (msg);
exit (Exit_MaintainerCause);
}
else
{
char *msg = CliOpt_InEnglish ? "SELECT mirror site: " : "选中镜像站: ";
say (xy_strjoin (5, msg, green (source->mirror->abbr), " (", green (source->mirror->code), ")"));
}
hr();
}
void
chsrc_determine_chgtype (enum ChgType_t type)
{
ProgMode_ChgType = is_reset_mode() ? ChgType_Reset : type;
}
#define MSG_EN_PUBLIC_URL "If the URL you specify is a public service, you are invited to contribute: chsrc issue"
#define MSG_CN_PUBLIC_URL "若您指定的URL为公有服务邀您参与贡献: chsrc issue"
#define MSG_EN_FULLY_AUTO "Fully-Auto changed source. "
#define MSG_CN_FULLY_AUTO "全自动换源完成"
#define MSG_EN_SEMI_AUTO "Semi-Auto changed source. "
#define MSG_CN_SEMI_AUTO "半自动换源完成"
#define MSG_EN_THANKS "Thanks to the mirror site: "
#define MSG_CN_THANKS "感谢镜像提供方: "
#define MSG_EN_BETTER "If you have a better source changing method , please help: chsrc issue"
#define MSG_CN_BETTER "若您有更好的换源方案,邀您帮助: chsrc issue"
#define MSG_EN_CONSTRAINT "Implementation constraints require manual operation according to the above prompts. "
#define MSG_CN_CONSTRAINT "因实现约束需按上述提示手工操作"
#define MSG_EN_STILL "Still need to operate manually according to the above prompts. "
#define MSG_CN_STILL "仍需按上述提示手工操作"
#define thank_mirror(msg) chsrc_log(xy_2strjoin(msg,purple(CliOpt_InEnglish?source->mirror->abbr:source->mirror->name)))
/**
* @param source 可为NULL
*
* @param [g]ProgMode_ChgType
*/
void
chsrc_conclude (Source_t *source)
{
hr();
// fprintf (stderr, "chsrc: now change type: %d\n", ProgMode_ChgType);
if (ProgMode_CMD_Reset || ChgType_Reset == ProgMode_ChgType)
{
// source_is_upstream (source)
char *msg = CliOpt_InEnglish ? "Has been reset to the upstream default source" : "已重置为上游默认源";
chsrc_log (purple (msg));
}
else if (ChgType_Auto == ProgMode_ChgType)
{
if (source)
{
if (source_is_userdefine (source))
{
char *msg = CliOpt_InEnglish ? MSG_EN_FULLY_AUTO MSG_EN_PUBLIC_URL \
: MSG_CN_FULLY_AUTO ", " MSG_CN_PUBLIC_URL;
chsrc_log (msg);
}
else
{
char *msg = CliOpt_InEnglish ? MSG_EN_FULLY_AUTO MSG_EN_THANKS \
: MSG_CN_FULLY_AUTO ", " MSG_CN_THANKS;
thank_mirror (msg);
}
}
else
{
char *msg = CliOpt_InEnglish ? MSG_EN_FULLY_AUTO : MSG_CN_FULLY_AUTO;
chsrc_log (msg);
}
}
else if (ChgType_SemiAuto == ProgMode_ChgType)
{
if (source)
{
if (source_is_userdefine (source))
{
char *msg = CliOpt_InEnglish ? MSG_EN_SEMI_AUTO MSG_EN_STILL MSG_EN_PUBLIC_URL \
: MSG_CN_SEMI_AUTO ", " MSG_CN_STILL "" MSG_CN_PUBLIC_URL;
chsrc_log (msg);
}
else
{
char *msg = CliOpt_InEnglish ? MSG_EN_SEMI_AUTO MSG_EN_STILL MSG_EN_THANKS \
: MSG_CN_SEMI_AUTO ", " MSG_CN_STILL "" MSG_CN_THANKS;
thank_mirror (msg);
}
}
else
{
char *msg = CliOpt_InEnglish ? MSG_EN_SEMI_AUTO MSG_EN_STILL \
: MSG_CN_SEMI_AUTO ", " MSG_CN_STILL;
chsrc_log (msg);
}
char *msg = CliOpt_InEnglish ? MSG_EN_BETTER : MSG_CN_BETTER;
chsrc_warn (msg);
}
else if (ChgType_Manual == ProgMode_ChgType)
{
if (source)
{
if (source_is_userdefine (source))
{
char *msg = CliOpt_InEnglish ? MSG_EN_CONSTRAINT MSG_EN_PUBLIC_URL \
: MSG_CN_CONSTRAINT "; " MSG_CN_PUBLIC_URL;
chsrc_log (msg);
}
else
{
char *msg = CliOpt_InEnglish ? MSG_EN_CONSTRAINT MSG_EN_THANKS \
: MSG_CN_CONSTRAINT ", " MSG_CN_THANKS;
thank_mirror (msg);
}
}
else
{
char *msg = CliOpt_InEnglish ? MSG_EN_CONSTRAINT : MSG_CN_CONSTRAINT;
chsrc_log (msg);
}
char *msg = CliOpt_InEnglish ? MSG_EN_BETTER : MSG_CN_BETTER;
chsrc_warn (msg);
}
else if (ChgType_Untested == ProgMode_ChgType)
{
if (source)
{
if (source_is_userdefine (source))
{
char *msg = CliOpt_InEnglish ? MSG_EN_PUBLIC_URL : MSG_CN_PUBLIC_URL;
chsrc_log (msg);
}
else
{
char *msg = CliOpt_InEnglish ? MSG_EN_THANKS : MSG_CN_THANKS;
thank_mirror (msg);
}
}
else
{
char *msg = CliOpt_InEnglish ? "Auto changed source" : "自动换源完成";
chsrc_log (msg);
}
char *msg = CliOpt_InEnglish ? "The method hasn't been tested or has any feedback, please report usage: chsrc issue" : "该换源步骤已实现但未经测试或存在任何反馈,请报告使用情况: chsrc issue";
chsrc_warn (msg);
}
else
{
fprintf (stderr, "chsrc: Wrong change type: %d\n", ProgMode_ChgType);
xy_unreached();
}
}
void
chsrc_ensure_root ()
{
char *euid = getenv ("$EUID");
if (NULL==euid)
{
char *buf = xy_run ("id -u", 0);
if (0!=atoi(buf)) goto not_root;
else return;
}
else
{
if (0!=atoi(euid)) goto not_root;
else return;
}
char *msg = NULL;
not_root:
msg = CliOpt_InEnglish ? "Use sudo before the command or switch to root to ensure the necessary permissions"
: "请在命令前使用 sudo 或切换为root用户来保证必要的权限";
chsrc_error (msg);
exit (Exit_UserCause);
}
void
chsrc_run (const char *cmd, int run_option)
{
if (ProgMode_Run_as_a_Service)
{
run_option |= RunOpt_Dont_Notify_On_Success|RunOpt_No_Last_New_Line;
}
else
{
if (CliOpt_InEnglish)
xy_log_brkt (blue (App_Name), bdblue ("RUN"), blue (cmd));
else
xy_log_brkt (blue (App_Name), bdblue ("运行"), blue (cmd));
}
if (CliOpt_DryRun)
{
return; // Dry Run 此时立即结束,并不真正执行
}
int status = system (cmd);
if (0==status)
{
if (! (RunOpt_Dont_Notify_On_Success & run_option))
{
log_cmd_result (true, status);
}
}
else
{
log_cmd_result (false, status);
if (! (run_option & RunOpt_Dont_Abort_On_Failure))
{
char *msg = CliOpt_InEnglish ? "Fatal error, forced end" : "关键错误,强制结束";
chsrc_error (msg);
exit (Exit_ExternalError);
}
}
if (! (RunOpt_No_Last_New_Line & run_option))
{
br();
}
}
static void
_chsrc_run_as_a_service (const char *cmd)
{
int run_option = RunOpt_Default;
ProgMode_Run_as_a_Service = true;
run_option |= RunOpt_Dont_Notify_On_Success|RunOpt_No_Last_New_Line;
chsrc_run (cmd, run_option);
ProgMode_Run_as_a_Service = false;
}
void
chsrc_view_file (const char *path)
{
char *cmd = NULL;
path = xy_normalize_path (path);
if (xy_on_windows)
{
cmd = xy_2strjoin ("type ", path);
}
else
{
cmd = xy_2strjoin ("cat ", path);
}
_chsrc_run_as_a_service (cmd);
}
void
chsrc_ensure_dir (const char *dir)
{
dir = xy_normalize_path (dir);
if (xy_dir_exist (dir))
{
return;
}
// 不存在就生成
char *mkdir_cmd = NULL;
if (xy_on_windows)
{
mkdir_cmd = "md "; // 已存在时返回 errorlevel = 1
}
else
{
mkdir_cmd = "mkdir -p ";
}
char *cmd = xy_2strjoin (mkdir_cmd, dir);
cmd = xy_str_to_quietcmd (cmd);
_chsrc_run_as_a_service (cmd);
char *msg = CliOpt_InEnglish ? "Directory doesn't exist, created automatically " : "目录不存在,已自动创建 ";
chsrc_note2 (xy_2strjoin (msg, dir));
}
void
chsrc_append_to_file (const char *str, const char *filename)
{
if (CliOpt_DryRun)
{
goto log_anyway;
}
char *file = xy_normalize_path (filename);
char *dir = xy_parent_dir (file);
chsrc_ensure_dir (dir);
FILE *f = fopen (file, "a");
if (NULL==f)
{
char *msg = CliOpt_InEnglish ? xy_2strjoin ("Unable to open file to write: ", file)
: xy_2strjoin ("无法打开文件以写入: ", file);
chsrc_error2 (msg);
exit (Exit_UserCause);
}
size_t len = strlen (str);
size_t ret = fwrite (str, len, 1, f);
if (ret != 1)
{
char *msg = CliOpt_InEnglish ? xy_2strjoin ("Write failed to ", file)
: xy_2strjoin ("写入文件失败: ", file);
chsrc_error2 (msg);
exit (Exit_UserCause);
}
fclose (f);
log_anyway:
/* 输出recipe指定的文件名 */
log_write_op (filename);
/*
char *cmd = NULL;
if (xy_on_windows)
{
cmd = xy_strjoin (4, "echo ", str, " >> ", file);
}
else
{
cmd = xy_strjoin (4, "echo '", str, "' >> ", file);
}
chsrc_run_a_service (cmd);
*/
}
void
chsrc_prepend_to_file (const char *str, const char *filename)
{
if (CliOpt_DryRun)
{
goto log_anyway;
}
char *file = xy_normalize_path (filename);
char *dir = xy_parent_dir (file);
chsrc_ensure_dir (dir);
char *cmd = NULL;
if (xy_on_windows)
{
xy_unimplemented();
}
else
{
cmd = xy_strjoin (4, "sed -i '1i ", str, "' ", file);
}
_chsrc_run_as_a_service (cmd);
log_anyway:
/* 输出recipe指定的文件名 */
log_write_op (filename);
}
void
chsrc_overwrite_file (const char *str, const char *filename)
{
if (CliOpt_DryRun)
{
goto log_anyway;
}
char *file = xy_normalize_path (filename);
char *dir = xy_parent_dir (file);
chsrc_ensure_dir (dir);
char *cmd = NULL;
if (xy_on_windows)
{
cmd = xy_strjoin (4, "echo ", str, " > ", file);
}
else
{
cmd = xy_strjoin (4, "echo '", str, "' > ", file);
}
_chsrc_run_as_a_service (cmd);
log_anyway:
/* 输出recipe指定的文件名 */
log_write_op (filename);
}
void
chsrc_backup (const char *path)
{
if (CliOpt_DryRun)
{
goto log_anyway;
}
char *cmd = NULL;
bool exist = xy_file_exist (path);
if (!exist)
{
char *msg = CliOpt_InEnglish ? "File doesn't exist, skip backup: " : "文件不存在,跳过备份: ";
chsrc_note2 (xy_2strjoin (msg, path));
return;
}
if (xy_on_bsd || xy_on_macos)
{
/* BSD 和 macOS 的 cp 不支持 --backup 选项 */
cmd = xy_strjoin (5, "cp -f ", path, " ", path, ".bak");
}
else if (xy_on_windows)
{
/**
* @note /Y 表示覆盖
* @note 默认情况下会输出一个 "已复制 1个文件"
*/
cmd = xy_strjoin (5, "copy /Y ", path, " ", path, ".bak 1>nul");
}
else
{
/**
* @see https://github.com/RubyMetric/chsrc/issues/152#issuecomment-2542673273
*
* busybox cp 会在 stderr 输出 unrecognized option: version
* stderr 导入到 stdout以便我们 xy_run() 可以接受到输出
*
*/
char *ver = xy_run ("cp --version 2>&1", 1);
/* cp (GNU coreutils) 9.4 */
if (strstr (ver, "GNU coreutils"))
{
cmd = xy_strjoin (5, "cp ", path, " ", path, ".bak --backup='t'");
}
else
{
/* 非 GNU 的 cp 可能不支持 --backup ,如 busybox cp */
cmd = xy_strjoin (5, "cp -f ", path, " ", path, ".bak");
}
}
_chsrc_run_as_a_service (cmd);
log_anyway:
log_backup_op (path);
}
/**
* 检查过程中全程保持安静
*/
char *
chsrc_get_cpuarch ()
{
char *ret;
char *msg;
#if XY_On_Windows
SYSTEM_INFO info;
GetSystemInfo (&info);
WORD num = info.wProcessorArchitecture;
switch (num)
{
case PROCESSOR_ARCHITECTURE_AMD64:
ret = "x86_64"; break;
case PROCESSOR_ARCHITECTURE_ARM:
ret = "arm"; break;
case PROCESSOR_ARCHITECTURE_INTEL:
ret = "x86"; break;
case PROCESSOR_ARCHITECTURE_IA64:
ret = "IA-64"; break;
case PROCESSOR_ARCHITECTURE_UNKNOWN:
default:
msg = CliOpt_InEnglish ? "Unable to detect CPU type" : "无法检测到CPU类型";
chsrc_error (msg);
exit (Exit_UserCause);
}
return ret;
#else
bool exist;
exist = chsrc_check_program_quietly ("arch");
if (exist)
{
ret = xy_run ("arch", 0);
return ret;
}
exist = chsrc_check_program_quietly ("uname");
if (exist)
{
ret = xy_run ("uname -m", 0);
return ret;
}
else
{
msg = CliOpt_InEnglish ? "Unable to detect CPU type" : "无法检测到CPU类型";
chsrc_error (msg);
exit (Exit_UserCause);
}
#endif
}
int
chsrc_get_cpucore ()
{
int cores = 2;
#if XY_On_Windows
SYSTEM_INFO info;
GetSystemInfo (&info);
DWORD num = info.dwNumberOfProcessors;
cores = (int)num;
#else
long num = sysconf(_SC_NPROCESSORS_ONLN);
cores = (int)num;
#endif
return cores;
}