feat: 新增导入导出功能,支持备份和恢复 PATH 配置

- 添加 import_export 模块,实现 JSON 格式的导入导出
- 在界面中添加导入和导出按钮,并配置回调函数
- 更新配置文件,增加相关文本和状态提示
- 在非管理员权限下禁用导入按钮
This commit is contained in:
2026-03-25 19:49:47 +08:00
parent ce232cb024
commit 55d0f80743
9 changed files with 498 additions and 64 deletions
+241
View File
@@ -0,0 +1,241 @@
#include "core/import_export.h"
#include "utils/os_env.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
// 获取当前日期时间
static void get_current_datetime(char *buffer, int size)
{
time_t now = time(NULL);
struct tm *tm_info = localtime(&now);
strftime(buffer, size, "%Y-%m-%d %H:%M:%S", tm_info);
}
// 转义 JSON 字符串中的特殊字符
static char *escape_json_string(const char *str)
{
if (!str)
return NULL;
int len = strlen(str);
char *result = (char *)malloc(len * 2 + 1);
if (!result)
return NULL;
char *p = result;
for (int i = 0; i < len; i++)
{
switch (str[i])
{
case '\\':
*p++ = '\\';
*p++ = '\\';
break;
case '"':
*p++ = '\\';
*p++ = '"';
break;
case '\n':
*p++ = '\\';
*p++ = 'n';
break;
case '\r':
*p++ = '\\';
*p++ = 'r';
break;
case '\t':
*p++ = '\\';
*p++ = 't';
break;
default:
*p++ = str[i];
break;
}
}
*p = '\0';
return result;
}
// 导出 PATH 到 JSON 文件
int export_paths_to_file(const StringList *list, const char *filepath, int is_system)
{
if (!list || !filepath)
return -1;
FILE *fp = fopen(filepath, "w, ccs=UTF-8");
if (!fp)
return -1;
char datetime[64];
get_current_datetime(datetime, sizeof(datetime));
fprintf(fp, "{\n");
fprintf(fp, " \"version\": \"%s\",\n", EXPORT_VERSION);
fprintf(fp, " \"type\": \"%s\",\n", is_system ? "SYSTEM" : "USER");
fprintf(fp, " \"exported\": \"%s\",\n", datetime);
fprintf(fp, " \"paths\": [\n");
for (int i = 0; i < list->count; i++)
{
if (list->items[i])
{
char *escaped = escape_json_string(list->items[i]);
if (escaped)
{
fprintf(fp, " \"%s\"%s\n", escaped, (i < list->count - 1) ? "," : "");
free(escaped);
}
}
}
fprintf(fp, " ]\n");
fprintf(fp, "}\n");
fclose(fp);
return 0;
}
// 移除字符串首尾的空格、制表符、换行符和回车符
static void trim_whitespace(char *str)
{
char *start = str;
while (*start == ' ' || *start == '\t')
start++;
char *end = str + strlen(str) - 1;
while (end > start && (*end == ' ' || *end == '\t' || *end == '\n' || *end == '\r'))
*end-- = '\0';
if (start != str)
memmove(str, start, strlen(start) + 1);
}
// 检查是否为注释行或空行
static int is_comment_or_empty(const char *line)
{
while (*line == ' ' || *line == '\t')
line++;
if (*line == '#' || *line == '\0')
return 1;
return 0;
}
// 检查是否为 JSON 文件
static int is_json_file(const char *filepath)
{
const char *ext = strrchr(filepath, '.');
return ext && strcasecmp(ext, ".json") == 0;
}
// 从文件导入 PATH
int import_paths_from_file(const char *filepath, StringList *list)
{
if (!filepath || !list)
return -1;
if (is_json_file(filepath))
{
FILE *fp = fopen(filepath, "r, ccs=UTF-8");
if (!fp)
return -1;
clear_string_list(list);
char buffer[8192];
int in_paths = 0;
int depth = 0;
int in_string = 0;
char path_buffer[4096];
int path_len = 0;
while (fgets(buffer, sizeof(buffer), fp))
{
char *p = buffer;
while (*p)
{
if (*p == '"' && (p == buffer || *(p - 1) != '\\'))
{
in_string = !in_string;
}
else if (in_string && *p == '\\')
{
p++;
if (*p)
{
if (*p == 'n')
*p = '\n';
else if (*p == 'r')
*p = '\r';
else if (*p == 't')
*p = '\t';
}
}
else if (!in_string)
{
if (*p == '{' || *p == '[')
depth++;
else if (*p == '}' || *p == ']')
depth--;
else if (*p == ':')
in_paths = (depth == 1);
else if (in_paths && depth == 2 && *p == '"')
{
path_len = 0;
p++;
while (*p && path_len < (int)sizeof(path_buffer) - 1)
{
if (*p == '"' && *(p - 1) != '\\')
break;
if (*p == '\\' && *(p + 1))
{
p++;
if (*p == 'n')
*p = '\n';
else if (*p == 'r')
*p = '\r';
else if (*p == 't')
*p = '\t';
}
path_buffer[path_len++] = *p++;
}
if (path_len > 0)
{
path_buffer[path_len] = '\0';
add_string_list(list, path_buffer);
}
}
}
p++;
}
}
fclose(fp);
return 0;
}
else
{
FILE *fp = fopen(filepath, "r, ccs=UTF-8");
if (!fp)
return -1;
clear_string_list(list);
char line[4096];
while (fgets(line, sizeof(line), fp))
{
trim_whitespace(line);
if (is_comment_or_empty(line))
continue;
add_string_list(list, line);
}
fclose(fp);
return 0;
}
}
+91 -44
View File
@@ -6,69 +6,116 @@
#include <stdlib.h>
#include <string.h>
static lua_State* G_L = NULL;
static lua_State *G_L = NULL;
static int G_loaded = 0;
static const char* G_config_path = "lua/config.lua";
static const char *G_config_path = "lua/config.lua";
static const char* get_string_default(const char* section, const char* key)
static const char *get_string_default(const char *section, const char *key)
{
if (strcmp(section, "app") == 0)
{
if (strcmp(key, "name") == 0) return "PathEditor";
if (strcmp(key, "name_readonly") == 0) return "PathEditor (只读模式)";
if (strcmp(key, "name") == 0)
return "PathEditor";
if (strcmp(key, "name_readonly") == 0)
return "PathEditor (只读模式)";
}
else if (strcmp(section, "dialog") == 0)
{
if (strcmp(key, "size") == 0) return "800x800";
if (strcmp(key, "minsize") == 0) return "800x800";
if (strcmp(key, "select_dir") == 0) return "选择目录";
if (strcmp(key, "size") == 0)
return "800x800";
if (strcmp(key, "minsize") == 0)
return "800x800";
if (strcmp(key, "select_dir") == 0)
return "选择目录";
}
else if (strcmp(section, "list") == 0)
{
if (strcmp(key, "item_padding") == 0) return "5x5";
if (strcmp(key, "backcolor") == 0) return "255 255 255";
if (strcmp(key, "item_padding") == 0)
return "5x5";
if (strcmp(key, "backcolor") == 0)
return "255 255 255";
}
else if (strcmp(section, "button") == 0)
{
if (strcmp(key, "rastersize") == 0) return "100x32";
if (strcmp(key, "new") == 0) return "新建(N)";
if (strcmp(key, "edit") == 0) return "编辑(E)";
if (strcmp(key, "browse") == 0) return "浏览(B)...";
if (strcmp(key, "del") == 0) return "删除(D)";
if (strcmp(key, "up") == 0) return "上移(U)";
if (strcmp(key, "down") == 0) return "下移(O)";
if (strcmp(key, "clean") == 0) return "一键清理";
if (strcmp(key, "ok") == 0) return "确定";
if (strcmp(key, "cancel") == 0) return "取消";
if (strcmp(key, "help") == 0) return "帮助(?)";
if (strcmp(key, "rastersize") == 0)
return "100x32";
if (strcmp(key, "new") == 0)
return "新建(N)";
if (strcmp(key, "edit") == 0)
return "编辑(E)";
if (strcmp(key, "browse") == 0)
return "浏览(B)...";
if (strcmp(key, "del") == 0)
return "删除(D)";
if (strcmp(key, "up") == 0)
return "上移(U)";
if (strcmp(key, "down") == 0)
return "下移(O)";
if (strcmp(key, "clean") == 0)
return "一键清理";
if (strcmp(key, "import") == 0)
return "导入(I)";
if (strcmp(key, "export") == 0)
return "导出(E)";
if (strcmp(key, "ok") == 0)
return "确定";
if (strcmp(key, "cancel") == 0)
return "取消";
if (strcmp(key, "help") == 0)
return "帮助(?)";
}
else if (strcmp(section, "label") == 0)
{
if (strcmp(key, "title") == 0) return "环境变量编辑器:";
if (strcmp(key, "search_placeholder") == 0) return "输入关键词搜索...";
if (strcmp(key, "tab_sys") == 0) return "系统变量 (System)";
if (strcmp(key, "tab_user") == 0) return "用户变量 (User)";
if (strcmp(key, "title") == 0)
return "环境变量编辑器:";
if (strcmp(key, "search_placeholder") == 0)
return "输入关键词搜索...";
if (strcmp(key, "tab_sys") == 0)
return "系统变量 (System)";
if (strcmp(key, "tab_user") == 0)
return "用户变量 (User)";
if (strcmp(key, "export_title") == 0)
return "导出 PATH";
if (strcmp(key, "import_title") == 0)
return "导入 PATH";
}
else if (strcmp(section, "layout") == 0)
{
if (strcmp(key, "vbox_gap") == 0) return "5";
if (strcmp(key, "vbox_margin") == 0) return "0x0";
if (strcmp(key, "vbox_all_margin") == 0) return "10x10";
if (strcmp(key, "vbox_all_gap") == 0) return "5";
if (strcmp(key, "hbox_gap") == 0) return "10";
if (strcmp(key, "hbox_margin") == 0) return "10x10";
if (strcmp(key, "hbox_alignment") == 0) return "ACENTER";
if (strcmp(key, "vbox_gap") == 0)
return "5";
if (strcmp(key, "vbox_margin") == 0)
return "0x0";
if (strcmp(key, "vbox_all_margin") == 0)
return "10x10";
if (strcmp(key, "vbox_all_gap") == 0)
return "5";
if (strcmp(key, "hbox_gap") == 0)
return "10";
if (strcmp(key, "hbox_margin") == 0)
return "10x10";
if (strcmp(key, "hbox_alignment") == 0)
return "ACENTER";
}
else if (strcmp(section, "status") == 0)
{
if (strcmp(key, "normal") == 0) return "状态: 就绪";
if (strcmp(key, "readonly") == 0) return "状态: ⚠️ 只读模式 (无管理员权限)";
if (strcmp(key, "saving") == 0) return "状态: 保存中...";
if (strcmp(key, "saved") == 0) return "状态: ✓ 保存成功";
if (strcmp(key, "error") == 0) return "状态: ✗ 保存失败";
if (strcmp(key, "deleted") == 0) return "状态: 已删除选中项";
if (strcmp(key, "loaded") == 0) return "状态: 已加载系统和用户变量";
if (strcmp(key, "drag_folder_only") == 0) return "提示: 只能拖拽文件夹添加到 PATH";
if (strcmp(key, "normal") == 0)
return "状态: 就绪";
if (strcmp(key, "readonly") == 0)
return "状态: ⚠️ 只读模式 (无管理员权限)";
if (strcmp(key, "saving") == 0)
return "状态: 保存中...";
if (strcmp(key, "saved") == 0)
return "状态: ✓ 保存成功";
if (strcmp(key, "error") == 0)
return "状态: ✗ 保存失败";
if (strcmp(key, "deleted") == 0)
return "状态: 已删除选中项";
if (strcmp(key, "loaded") == 0)
return "状态: 已加载系统和用户变量";
if (strcmp(key, "drag_folder_only") == 0)
return "提示: 只能拖拽文件夹添加到 PATH";
if (strcmp(key, "admin_warning") == 0)
return "未检测到管理员权限,只能查看和导出 PATH,无法保存更改。";
}
return "";
}
@@ -90,7 +137,7 @@ int lua_config_init(void)
if (luaL_dofile(G_L, G_config_path) != LUA_OK)
{
const char* err = lua_tostring(G_L, -1);
const char *err = lua_tostring(G_L, -1);
if (err)
{
fprintf(stderr, "[Lua Config] 加载配置文件失败: %s\n", err);
@@ -115,7 +162,7 @@ void lua_config_destroy(void)
G_loaded = 0;
}
const char* lua_config_get_string(const char* section, const char* key)
const char *lua_config_get_string(const char *section, const char *key)
{
if (G_L == NULL || section == NULL || key == NULL)
{
@@ -143,13 +190,13 @@ const char* lua_config_get_string(const char* section, const char* key)
return get_string_default(section, key);
}
const char* value = lua_tostring(G_L, -1);
const char *value = lua_tostring(G_L, -1);
lua_settop(G_L, 0);
return value ? value : get_string_default(section, key);
}
int lua_config_get_int(const char* section, const char* key, int default_value)
int lua_config_get_int(const char *section, const char *key, int default_value)
{
if (G_L == NULL || section == NULL || key == NULL)
{