diff --git a/README.md b/README.md index 310b9ce..cfcdffb 100644 --- a/README.md +++ b/README.md @@ -8,35 +8,32 @@ **注**:使用安装包的用户,如果想变更安装位置的话,请先卸载旧版本(原位置更新最新版不用卸载)。 -# 一、简要介绍 +# 一、说明 -| 主要功能序号 | 简介 | -| ------------ | ------------------------------------------------------------ | -| 1 | A端提交文件列表到服务端,B端可以从服务端查阅有哪些客户端提交的哪些任务,自行选择下载。 | -| 2 | A端可以提交一个下载任务文件给B端,B端会自动下载列表中的文件(可用作更新远端文件)。 | -| 3 | A端可以直接给B端发送文件(v1.3.1及其以后版本)。 +**注意:** 当前说明文档仅针对`v1.4.1`及其以后的版本,此前的版本需要检出对应`tag`的`README.md`或者下载对应发布版本的源码,然后解压其中的`README.md`。 + +服务端程序支持任意个客户端相互之间**同时连接**和**同时传输**文件,吞吐瓶颈在服务端主机网络上。 - `tss`和`tsc`均为命令行端程序,无GUI。 -- `tsc`从`tss`下载文件的时候,如果本地有已存在则会被**覆盖**(注意)。 - -- 介绍所指的客户端`A`、`B`是泛指,实际服务端程序支持任意个客户端相互之间**同时连接**和**同时传输**文件,吞吐瓶颈在服务端主机网络上。 - ## 一些特点(基于最新版本) - 通配符传输。 -- 终端自动文件补全。 -- 自动更新远程文件。 -- 多客户端可以同时互相收发文件。 -- 自动检测对方掉线。 -- 服务端自我安全防御(若部署到云端防止常规攻击)。 - 广泛的平台支持。 -- 服务端仅转发数据,不存储数据。 +- 终端自动文件补全。 +- 自动检测对方掉线。 - 极小的、单个文件。 -- 干净,不会在客户机环境到处遗留临时文件。 +- 支持相对路径处理。 +- 单端可以操作下载发送。 +- 支持单客户端操作收发本地文件。 +- 多客户端可以同时互相收发文件。 +- 服务端仅转发数据,不存储数据。 - 运行时无其他三方二进制库依赖。 -- 临时公网传输,无需使用三方需要登陆软件。 - `Linux`到`Linux`文件传输保留原权限。 +- 干净,不会在客户机环境到处遗留临时文件。 +- 临时公网传输,无需使用三方需要登陆软件。 +- 服务端自我安全防御(若部署到云端防止常规攻击)。 +- 支持拒绝同时操作同设备同路径文件,防止文件内容丢失。 # 二、使用说明 @@ -44,76 +41,31 @@ - 对于服务端程序`tss`,绑定默认绑定`0.0.0.0`的`9898`端口,如果需要修改端口,使用参数启动,示例:`tss 9898`。 - 对于客户端程序`tsc`,请使用`tsc --help`查看使用方式。 -- `Up`指令后面的文件名路径,如果是非全路径(即相对路径),程序会自动拼接到当前`tsc`工作目录(如`Up dira/test.txt`也是可以的)。 -## 2.命令使用(截图可能过时,但使用方式大致如此) +## 2.使用 -### 2.1 客户端 tsc 简介 +`tsc`连接到服务端`tss`后,可以输入`h`查看所有指令的具体介绍。 ![tsc](https://www.sinxmiao.cn/taynpg/transm/raw/branch/main/img/tsc_use.png) -### 2.2 功能一简介 +## 3.介绍补充 -![func1](https://www.sinxmiao.cn/taynpg/transm/raw/branch/main/img/func1.png) +- 需要传入存储路径的指令,如果未填写,默认`tsc`当前工作目录。 +- `任务文件`格式统一为`UTF-8`编码。 +- 支持相对路径处理。 +- `UpTask`和`DownTask`指令`任务文件`格式完全一致,均为左文件,右目录,尽管是下载别人文件。 -### 2.3 功能二简介 - -![func2](https://www.sinxmiao.cn/taynpg/transm/raw/branch/main/img/func2.png) - -### 3.1 Get功能 - -`v1.2.3`版本起(含),当某个客户端的列表文件数量过多时,只展示部分(会显示总数量)。 - -### 3.2 Up功能 - -`v1.2.3`版本起(含),`up`后路径支持`?`和`*`通配符: - -```shell -up /home/zhang/png/cloud*.png|/home/zhang/download/202?-*.exe -``` - -### 3.3 Down功能 - -`v1.2.3`版本起(含),支持传入下载位置(默认命令所在目录): - -```shell -down 1 dira/dirb -down 1 ../download -down 2 /home/zhang/document -``` - -### 3.4 Update功能 - -命令格式为:`Update 客户端标号 列表文件` - -`Update`的提交的列表文件格式为`txt`,内容为每一行格式是`A|B`,其中`A`为提交端的文件路径,`B`为要放到下载端的哪个目录 **(下载端必须存在这个目录,否则下载端拒绝自动下载)**。 - -示例执行:`Update 1 task.txt`,其中`task.txt`内容示例如下: - -```txt -D:/文件/abc.zip|/home/zhangsan/downlaod -D:/截图/Ni.jpg|/home/zhangsan/picture -``` - -#### update新增(一) - -`v1.2.1`版本起(含),`task.txt`支持以下变量: +具体`任务文件`内容格式如下(左侧发送端,右侧接受端),内容支持变量: - ${HOME},用户目录(发送端接收端均支持)。 -- ${CURRENT},任务文件所在目录(即`task.txt`所在目录,该变量仅支持发送端,也就是`|`左侧,因为接收端没有任务文件所在路径) +- ${CURRENT},任务文件所在目录(该变量仅支持发送端,也就是`|`左侧,因为接收端没有任务文件所在路径) ```txt ${HOME}/截图/Ni.jpg|${HOME}/dira ${CURRENT}/xxx.zip|D:\ ``` -**NOTE**: `列表文件`的格式为`UTF-8`编码。 - -# 注意 - -- 如果两个`tsc`客户端在同一台机器上同时收发同一个文件将导致文件丢失损坏(如果收发操作的是同一个文件)。 - -# 编译 +# 三、编译 当前项目支持`cmake`构建工具。 diff --git a/client/client.cpp b/client/client.cpp index c32f6c3..d040971 100644 --- a/client/client.cpp +++ b/client/client.cpp @@ -57,7 +57,7 @@ void CClient::print_help(bool detail) TLOGW("SupportCmd ==>"); if (!detail) { - TLOGI("Get|Who|Where|Ls|Sub|Fetch|Up|Down|UpTask|DownTask"); + TLOGW("Get|Who|Where|Ls|Sub|Fetch|Up|Down|UpTask|DownTask"); TLOGI("You can use 'h' to show cmd's detail."); TLOGI("You can use 'end' or 'ctrl-c' to exit."); return; @@ -152,12 +152,13 @@ void CClient::run(const std::string& ip, const std::string& port, const std::str std::thread thread([&]() { io_context_.run(); }); th_down_active_ = std::thread([&]() { judget_down_active(); }); + print_help(false); + fc_append('|'); get_id(); + if (ip == "127.0.0.1") { get_clients(); } - print_help(false); - fc_append('|'); while (1) { char* readline = fc_readline(); @@ -210,7 +211,11 @@ void CClient::run(const std::string& ip, const std::string& port, const std::str param.erase(0, param.find_first_of(" ") + 1); if (scmd == "UpTask" || scmd == "uptask" || scmd == "ut") { - cmd_uptask(param); + cmd_sub_task(param, true); + continue; + } + if (scmd == "DownTask" || scmd == "downtask" || scmd == "dt") { + cmd_sub_task(param, false); continue; } if (scmd == "Up" || scmd == "up") { @@ -412,7 +417,7 @@ bool CClient::cmd_upload_files(const std::string& param) return false; } - auto handel_ret = handle_user_select(mre); + auto handel_ret = handle_user_select(mre, true); if (handel_ret.empty()) { TLOGE("handle_user_select not pass, abort action!"); return false; @@ -544,7 +549,7 @@ void CClient::report_trans_ret(TransState state, const std::string& key) 功能为,请求某个客户端,更新我所列出的文件,右侧是远端需要存储的目录(必须存在,不存在则不理会) */ -bool CClient::cmd_uptask(const std::string& param) +bool CClient::cmd_sub_task(const std::string& param, bool is_send) { auto tvec = COfStr::split(param, " "); if (tvec.size() < 2) { @@ -564,20 +569,19 @@ bool CClient::cmd_uptask(const std::string& param) } const auto& sr = clients_[index]; - bool local_trans = false; - if (sr->id == own_id_) { - TLOGW("local_trans path handle mode!!!"); - local_trans = true; - } - // 读取list文件 std::string list_file_full = COfPath::to_full(list_file); - if (fs::is_directory(list_file_full)) { TLOGE("{} is a directory, only support file.", list_file_full); return false; } + bool local_trans = false; + if (sr->uuid == uuid_) { + TLOGW("local_trans path handle mode!!!"); + local_trans = true; + } + std::ifstream in(list_file_full); if (!in.is_open()) { TLOGE("Can't Open File:{}", list_file_full); @@ -589,6 +593,7 @@ bool CClient::cmd_uptask(const std::string& param) in.close(); #if defined(_WIN32) + // 从文件中读取的内容还是要手动转码一下。 content = CCodec::u8_to_ansi(content); #endif @@ -607,27 +612,17 @@ bool CClient::cmd_uptask(const std::string& param) if (v.size() >= 2) { auto pr = variable_handle(list_file_full, v[0], true); pr = COfPath::standardize(pr); - if (!fs::exists(pr)) { + if (is_send && !fs::exists(pr)) { TLOGE("file {} not exist.", pr); valid = false; break; } - TLOGI("--->check pass {}:{}", line, pr); - auto sec = v[1]; - if (local_trans) { - sec = variable_handle(list_file_full, v[1], true); - sec = COfPath::standardize(sec); - if (!fs::is_directory(sec) || !fs::exists(sec)) { - TLOGE("not directory or {} not exist.", pr); - valid = false; - break; - } - if (COfPath::is_same_dir(pr, sec)) { - TLOGE("Local Trans Can't Transfer {} to {}", pr, sec); - valid = false; - break; - } + if (is_send) { + TLOGI("--->check pass {}:{}", line, pr); } + auto sec = v[1]; + sec = variable_handle(list_file_full, sec, local_trans); + sec = COfPath::standardize(sec); mre[line++] = pr + "|" + sec; continue; } @@ -640,7 +635,7 @@ bool CClient::cmd_uptask(const std::string& param) return false; } - auto handel_ret = handle_user_select(mre); + auto handel_ret = handle_user_select(mre, is_send); if (handel_ret.empty()) { TLOGE("handle_user_select not pass, abort action!"); return false; @@ -648,7 +643,12 @@ bool CClient::cmd_uptask(const std::string& param) list_file_ = list_file_full; std::shared_ptr buf = std::make_shared(); - buf->type_ = TYPE_REQUEST_UPDATE_LIST; + + if (is_send) { + buf->type_ = TYPE_REQUEST_UPDATE_LIST; + } else { + buf->type_ = TYPE_REQUEST_DOWN_UPDATE_LIST; + } CMessageInfo msg_info; msg_info.str = handel_ret; serialize(msg_info, &buf->data_, buf->len_); @@ -661,7 +661,7 @@ bool CClient::cmd_uptask(const std::string& param) return true; } -bool CClient::check_update_list(const std::string& content, std::map& files) +bool CClient::variable_and_parse_files(const std::string& content, std::map& files) { auto vec = COfStr::split(content, "\n"); bool valid = true; @@ -870,6 +870,45 @@ std::vector CClient::load_line_his() return history; } +std::string CClient::variable_and_reverse_files(const std::string& source) +{ + auto vec = COfStr::split(source, "\n"); + std::string result; + bool valid = true; + for (const auto& item : vec) { + auto rl = COfStr::trim(item); + if (rl.empty()) { + continue; + } + auto vi = COfStr::split(rl, "|"); + if (vi.size() != 2) { + TLOGE("Size not 2 {}", item); + valid = false; + break; + } + auto pr = variable_handle("", vi[1], false); + if (!fs::exists(pr)) { + valid = false; + TLOGE("Not exist {}", pr); + break; + } + TLOGI("---> check pass {}", pr); + + fs::path ppr(pr); + fs::path pvi(vi[0]); + + auto lp = ppr.append(pvi.filename().string()).string(); + auto rp = pvi.parent_path().string(); + + auto line = lp + "|" + rp; + result = result + line + "\n"; + } + if (!valid) { + return ""; + } + return result; +} + bool CClient::save_uuid() { fs::path uuid_path(uuid_path_); @@ -1132,6 +1171,23 @@ void CClient::handle_frame(CFrameBuffer* buf) report_trans_ret(TRANS_FAILED); break; } + case TYPE_REQUEST_DOWN_UPDATE_LIST: { + CMessageInfo msg_info; + if (!deserialize(buf->data_, buf->len_, msg_info)) { + TLOGE("{} GetList deserialize failed.", __LINE__); + break; + } + delete[] buf->data_; + msg_info.str = variable_and_reverse_files(msg_info.str); + serialize(msg_info, &buf->data_, buf->len_); + std::swap(buf->tid_, buf->fid_); + buf->type_ = TYPE_REQUEST_UPDATE_LIST; + if (!send_frame(buf)) { + TLOGE("Send Failed {}.", __LINE__); + break; + } + break; + }; case TYPE_REQUEST_UPDATE_LIST: { std::map files; if (down_ && down_->trans_state_ == TRANS_REDAY) { @@ -1143,7 +1199,7 @@ void CClient::handle_frame(CFrameBuffer* buf) TLOGE("{} GetList deserialize failed.", __LINE__); break; } - if (check_update_list(msg_info.str, files)) { + if (variable_and_parse_files(msg_info.str, files)) { buf->type_ = TYPE_CONFIRM_UPDATE_LIST; } else { buf->type_ = TYPE_UNCONFIRM_UPDATE_LIST; @@ -1314,16 +1370,19 @@ void CClient::judget_down_active() } std::string CClient::variable_handle(const std::string& task_list_path, const std::string& source, - bool is_send) + bool is_local) { std::string result(source); // 支持的变量如下: // ${HOME} 用户目录(发送端接收端均支持) // ${CURRENT} 任务文件所在目录(该变量仅支持发送端,因为接收端没有任务文件所在路径) - if (source.find("${HOME}") != std::string::npos) { + if (is_local && source.find("${HOME}") != std::string::npos) { result = COfStr::replace(result, "${HOME}", COfPath::get_home()); } - if (is_send && source.find("${CURRENT}") != std::string::npos) { + if (is_local && source.find("${CURRENT}") != std::string::npos) { + if (task_list_path.empty()) { + return result; + } fs::path p(task_list_path); std::string list_dir = p.parent_path().string(); result = COfStr::replace(result, "${CURRENT}", list_dir); @@ -1331,14 +1390,25 @@ std::string CClient::variable_handle(const std::string& task_list_path, const st return result; } -std::string CClient::handle_user_select(const std::unordered_map& source) +std::string CClient::handle_user_select(const std::unordered_map& source, bool is_send) { std::string handled_content{}; std::string input{}; + if (!is_send) { + handled_content.clear(); // 清空之前的内容 + for (const auto& pair : source) { + handled_content.append(pair.second + "\n"); + } + return handled_content; + } + while (true) { TLOGI("numbers by space, or '0' use all, 'end' to quit: "); - std::getline(std::cin, input); + // std::getline(std::cin, input); + char* readline = fc_readline(); + input = std::string(readline); + fc_free(readline); if (input == "end") { handled_content.clear(); diff --git a/client/client.h b/client/client.h index 99e938b..1ec5fef 100644 --- a/client/client.h +++ b/client/client.h @@ -55,8 +55,8 @@ public: bool cmd_upload_files(const std::string& param); bool down_one_file(int remote_id, const std::string& file, const std::string& local_dir = ""); void report_trans_ret(TransState state, const std::string& key = ""); - bool cmd_uptask(const std::string& param); - bool check_update_list(const std::string& content, std::map& files); + bool cmd_sub_task(const std::string& param, bool is_send); + bool variable_and_parse_files(const std::string& content, std::map& files); bool down_update_file(const std::map& files); bool get_dir_files(const std::string& dir, std::string& out, std::string& error); bool cmd_ls(const std::string& param); @@ -66,6 +66,7 @@ private: bool send_frame(CFrameBuffer* buf); void save_line_his(const std::string& input); std::vector load_line_his(); + std::string variable_and_reverse_files(const std::string& source); bool save_uuid(); std::string read_uuid(); void get_id(); @@ -76,8 +77,8 @@ private: void send_file_data_th(const char* keys); void hearts(); void judget_down_active(); - std::string variable_handle(const std::string& task_list_path, const std::string& source, bool is_send); - std::string handle_user_select(const std::unordered_map& source); + std::string variable_handle(const std::string& task_list_path, const std::string& source, bool is_local); + std::string handle_user_select(const std::unordered_map& source, bool is_send); private: std::mutex mutex_; diff --git a/filecomplete b/filecomplete index c477dad..4b6612c 160000 --- a/filecomplete +++ b/filecomplete @@ -1 +1 @@ -Subproject commit c477dad67e171aecf9fb5b8ce361119bf459ff8f +Subproject commit 4b6612cc63f21b4d092a0b5731ceb7f817f20d23 diff --git a/img/func1.png b/img/func1.png deleted file mode 100644 index 0a9e465..0000000 Binary files a/img/func1.png and /dev/null differ diff --git a/img/func2.png b/img/func2.png deleted file mode 100644 index 045d5d2..0000000 Binary files a/img/func2.png and /dev/null differ diff --git a/img/tsc_use.png b/img/tsc_use.png index ab0e4f0..f417833 100644 Binary files a/img/tsc_use.png and b/img/tsc_use.png differ diff --git a/util/util.h b/util/util.h index 5601a13..a11df9a 100644 --- a/util/util.h +++ b/util/util.h @@ -29,6 +29,7 @@ enum FrameType : int16_t { TYPE_OFFLINE, TYPE_JUDGE_ACTIVE, TYPE_REQUEST_UPDATE_LIST, + TYPE_REQUEST_DOWN_UPDATE_LIST, TYPE_CONFIRM_UPDATE_LIST, TYPE_UNCONFIRM_UPDATE_LIST, TYPE_DONE_UPDATE_LIST,