#include "CurlFtp.h" #include #include #include #include "stdlog.h" #if (__cplusplus >= 201703L) #include #else #endif /* (__cplusplus >= 201703L) */ #include #include #if defined(_WIN32) #include #include #endif /* _WIN32 */ /* ================================================================================== * *********************************** 全局变量 ************************************* * ================================================================================== */ /* 是否有初始化实例,主要用于静态函数调用curl_global_cleanup()函数前判断,有实例就不调用 */ static bool hasInstace = false; /* 记录上传或者下载的时间,thread_local是C++11加入的关键字,表示在每个线程中都有自己的lastTime */ static thread_local std::chrono::system_clock::time_point lastTime; static thread_local uint64_t dCount; static thread_local uint64_t uCount; static thread_local uint64_t dSpeed; /* 保存最后的下载速度 */ static thread_local uint64_t uSpeed; /* 保存最后的上传速度 */ /* ================================================================================== * *********************************** 全局函数 ************************************* * ================================================================================== */ /** * @brief 写入回调函数 * * @param contents curl的数据缓冲区 * @param size 数据大小,单位是size_t * @param nmemb size_t的单位字节数 * @param userStr 用户提供的字符串,格式可以是任意的 * @return size_t 总共获取到的数据大小,单位是字节 */ static size_t writeStringCallback(void *contents, size_t size, size_t nmemb, std::string *userStr) { // size_t newLength = size * nmemb; // size_t oldLength = userStr->size(); // try // { // userStr->resize(oldLength + newLength); // } // catch(std::bad_alloc &e) // { // //handle memory problem // return 0; // } // std::copy_n((char*)contents, newLength, userStr->begin() + oldLength); userStr->append((char*)contents, size * nmemb); return size * nmemb; } /** * @brief 写入回调函数,listFiles需要调用 * * @param buffer curl下载回来的数据 * @param size 数据大小 * @param nmemb 数据单位字节数 * @param userp 用户传进来的容器 * @return int 返回拷贝的字节数 */ static int writeStringListCallback(void* buffer, size_t size, size_t nmemb, void* userp) { std::vector* fileList = static_cast*>(userp); std::string line(static_cast(buffer), size * nmemb); // printf("line = %s\n", line.c_str()); fileList->push_back(line); return size * nmemb; } /** * @brief 写入文件回调函数 * * @param contents 读取到的数据内容 * @param size 数据大小 * @param nmemb 数据单位 * @param pFile 文件指针 * @return size_t 实际读取的大小 */ static size_t writeFileCallBack(void* contents, size_t size, size_t nmemb, std::ostream* pFile) { pFile->write(reinterpret_cast(contents), size * nmemb); return size * nmemb; } /** * @brief 写入文件回调函数,使用Qt的QFile写文件,解决Windows下中文路径的问题 * * @param contents * @param size * @param nmemb * @param pFile * @return size_t */ static size_t writeFileCallBack_QT(void* contents, size_t size, size_t nmemb, QFile* pFile) { qint64 written = pFile->write(reinterpret_cast(contents), size * nmemb); if (written == -1) { // Handle write error return 0; } return static_cast(written); } /** * @brief 写入数据到vector中 * * @param contents * @param size * @param nmemb * @param vecData * @return size_t */ static size_t writeDataCallBack(void* contents, size_t size, size_t nmemb, std::vector* vecData) { size_t copySize = size * nmemb; vecData->insert(vecData->end(), (char*)contents, (char*)contents + copySize); return copySize; } /** * @brief 读取文件回调函数 * * @param contents 下载到的数据内容 * @param size 数据大小 * @param nmemb 数据单位 * @param pFile 文件指针 * @return size_t 写入的大小 */ static size_t readFileCallBack(void* contents, size_t size, size_t nmemb, std::istream* pFile) { pFile->read(reinterpret_cast(contents), size * nmemb); /* 获取读取到的字节数,可能读取到文件末尾,所以不能直接使用传入的字节数 */ size_t readSize = pFile->gcount(); return readSize; } /** * @brief * * @param contents ftp需要的目标内容 * @param size 拷贝的数据大小 * @param nmemb 单个数据的字节数 * @param pData 源指针 * @return size_t 已拷贝的数据大小 */ static size_t readDataCallBack(void* contents, size_t size, size_t nmemb, CF_ArrayInfo* pData) { if(pData == nullptr) { return 0; } /* 判断是否还够本次拷贝的字节数 */ size_t copySize = size * nmemb; if(pData->size - pData->pos < copySize) { copySize = pData->size - pData->pos; } memcpy(contents, pData->data + pData->pos, copySize); pData->pos += copySize; return copySize; } /** * @brief 计算速度 * * @param speed * @param retSpeed * @param unit */ void computeSpeed(uint64_t speed, double& retSpeed, std::string& unit) { double KB = speed / 1024.0; double MB = KB / 1024.0; double GB = MB / 1024.0; if(GB > 1) { unit = "GB/S"; retSpeed = GB; } else if(MB > 1) { unit = "MB/S"; retSpeed = MB; } else if(KB > 1) { unit = "KB/S"; retSpeed = KB; } else { unit = "B/S"; retSpeed = speed; } } /** * @brief 打印进度条 * * @param type 上传还是下载类型 * @param total 总大小,单位字节 * @param now 现在的进度,单位字节 */ void printProgress(CF_TransType type, curl_off_t total, curl_off_t now) { std::string transType; uint64_t count = 0; if(type == CF_TransType::DOWNLOAD) { transType = "Download"; count = now - dCount; dCount = now; } else if (type == CF_TransType::UPLOAD) { transType = "Upload"; count = now - uCount; uCount = now; } /* 计算进度,百分比 */ double percent = (double)now / (double)total * 100; /* 计算单位 */ double tKB = total / 1024.0; double tMB = tKB / 1024.0; double tGB = tMB / 1024.0; /* 计算速度 */ double speed = 0.0; std::string unit; computeSpeed(count, speed, unit); // if(speed == 0.0) // { // if(CF_TransType::DOWNLOAD == type) // { // speed = dSpeed; // }else if (CF_TransType::UPLOAD == type) { // speed = uSpeed; // } // }else { // if(CF_TransType::DOWNLOAD == type) // { // dSpeed = speed; // }else if (CF_TransType::UPLOAD == type) { // uSpeed = speed; // } // } if(tGB > 1) { double dGB = now / 1024.0 / 1024.0 / 1024.0; double speed = count / 1024.0 / 1024.0; printf("%s Total / now : %.2fGB / %.2fGB, %.2f%%, Speed:%.2f %s\r", transType.c_str(), tGB, dGB, percent, speed, unit.c_str()); // printf("Download Total / now : {:.2f}GB / {:.2f}GB, {:.2f}%\r", tGB, dGB, percent); } else if(tMB > 1) { double dMB = now / 1024.0 / 1024.0; printf("%s Total / now : %.2fMB / %.2fMB, %.2f%%, Speed:%.2f %s\r", transType.c_str(), tMB, dMB, percent, speed, unit.c_str()); // printf("Download Total / now : {:.2f}MB / {:.2f}MB, {:.2f}%\r", tMB, dMB, percent); } else if(tKB > 1) { double dKB = now / 1024.0; printf("%s Total / now : %.2fKB / %.2fKB, %.2f%%, Speed:%.2f %s\r", transType.c_str(), tKB, dKB, percent, speed, unit.c_str()); } else { printf("%s Total / now : %lldB / %lldB, %.2f%%, Speed:%.2f %s\r", transType.c_str(), static_cast(total), static_cast(now), percent, speed, unit.c_str()); } fflush(stdout); } /** * @brief 上传和下载进度回调函数,这个函数kennel会被多次调用,即使是没有在下载的时候,因此需要判断传入的数据是否是0 * 必须将 CURLOPT_NOPROGRESS 设为 0 才能真正调用该函数。 * * @param clientp 通过CURLOPT_XFERINFODATA 设置的指针,libcurl 不会使用它,只会将其从应用程序传递给回调函数。 * 可以通过这个指针将下载的进度传递给应用程序 * @param dltotal 下载的总字节数,上传的时候这个为0 * @param dlnow 已经下载的总字节数 * @param ultotal 需要上传的总字节数,下载的时候这个为0 * @param ulnow 已经上传的总字节数 * @return int 返回0表示正常,非0表示异常 */ static int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { // LOG_DEBUG("dTotal: {}, dNow: {}, uTotal: {}, uNow: {}", dltotal, dlnow, ultotal, ulnow); if(dltotal == 0 && ultotal == 0) { dCount = 0; uCount = 0; dSpeed = 0; uSpeed = 0; return 0; } std::chrono::system_clock::time_point nowTime = std::chrono::system_clock::now(); auto duration = std::chrono::duration_cast(nowTime - lastTime).count(); // LOG_DEBUG("duration:{}", duration); if((duration < 1000) && ((dltotal != dlnow) || (ultotal != ulnow))) { return 0; } lastTime = nowTime; /* 正在下载 */ if(dltotal > 0) { printProgress(CF_TransType::DOWNLOAD, dltotal, dlnow); // printf("Download Total / now : {} / {}, {:.2f}%\r", dltotal, dlnow, downloadPercent); } /* 正在上传 */ else if(ultotal > 0) { printProgress(CF_TransType::UPLOAD, ultotal, ulnow); // double uploadPercent = (double)ulnow / (double)ultotal * 100; // printf("Upload Total / now : {} / {}, {:.2f}%\r", ultotal, ulnow, uploadPercent); } return 0; } /* ======================== 通配符列表示例所需回调 ======================== */ /* 分块开始回调:收集文件信息并跳过实际传输 */ static long chunk_bgn_cb(const void* transfer_info, void* ptr, int /*remains*/) { const struct curl_fileinfo* finfo = static_cast(transfer_info); if(finfo == nullptr || ptr == nullptr) { return CURL_CHUNK_BGN_FUNC_OK; } std::vector* out = static_cast*>(ptr); // 跳过 "." 和 ".." 等无效名称 if(finfo->filename && (std::string(finfo->filename) == "." || std::string(finfo->filename) == "..")) { return CURL_CHUNK_BGN_FUNC_SKIP; } CF_FileInfo fi; if(finfo->filetype == CURLFILETYPE_DIRECTORY) { fi.type = CF_FileType::DIR; } else if(finfo->filetype == CURLFILETYPE_FILE) { fi.type = CF_FileType::FILE; } else { // 其他类型(链接、设备等)不处理 return CURL_CHUNK_BGN_FUNC_SKIP; } fi.name = (finfo->filename ? std::string(finfo->filename) : std::string()); fi.size = static_cast(finfo->size); out->push_back(std::move(fi)); // 仅做枚举,不实际下载数据 return CURL_CHUNK_BGN_FUNC_SKIP; } /* 分块结束回调:无特别处理 */ static long chunk_end_cb(void* /*ptr*/) { return CURL_CHUNK_END_FUNC_OK; } /* 使用Windows API进行编码转换 */ #if defined(_WIN32) static char* GBToUTF8(const char* gb2312) { int len = MultiByteToWideChar(CP_ACP, 0, gb2312, -1, NULL, 0); wchar_t* wstr = new wchar_t[len+1]; memset(wstr, 0, len+1); MultiByteToWideChar(CP_ACP, 0, gb2312, -1, wstr, len); len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL); char* str = new char[len+1]; memset(str, 0, len+1); WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL); if(wstr) delete[] wstr; return str; } #endif /* _WIN32 */ /* 正则表达式,匹配多个空格 */ const std::regex parseRegSpace(R"(( )+)"); /* 匹配以非空格开头的字符,空格隔开,中间任意字符,空格隔开,非空格组成的结尾 * (文件类型) ... (文件大小,$5) ... (文件名) */ const std::regex parseReg1(R"(^([^ ]+) (\d*) (\w*) (\w*) (\d*) (.*) ([^ ]+)$)"); // LOG_INFO("\n-------------------\n"); /* 匹配换行符,分割每一行 */ const std::regex parseReg2(R"(\n)"); /* 匹配文件夹 */ const std::regex parseReg3(R"(^d.*)"); /* 匹配文件 */ const std::regex parseReg4(R"(^-.*$)"); /** * @brief 解析字符串文件信息,如果FTP服务器规定好个编码,不需要进行转换 * * @param strSrc 读取到的字符串 * @param fileList 文件信息列表 * @return true * @return false */ static bool parseFileInfo(const std::string& strSrc, std::vector& fileInfo) { // SPDLOG_DEBUG("SRC FILE INFO:\n{}", strSrc); #if defined(_WIN32) // auto str1 = GBToUTF8(strSrc.c_str()); std::string str2 = strSrc; #else std::string str2 = strSrc; #endif /* _WIN32 */ // LOG_DEBUG("\n{}", str2); // /* 正则表达式,匹配多个空格 */ // std::regex regSpace(R"(( )+)"); // /* 匹配以非空格开头的字符,空格隔开,中间任意字符,空格隔开,非空格组成的结尾 // * (文件类型) ... (文件大小,$5) ... (文件名) // */ // std::regex reg1(R"(^([^ ]+) (\d*) (\w*) (\w*) (\d*) (.*) ([^ ]+)$)"); // // LOG_INFO("\n-------------------\n"); // /* 匹配换行符,分割每一行 */ // std::regex reg2(R"(\n)"); // /* 匹配文件夹 */ // std::regex reg3(R"(^d.*)"); // /* 匹配文件 */ // std::regex reg4(R"(^-.*$)"); try { /* -1 表示对正则表达式之前的子序列感兴趣 * 0 表示对正则表达式本身感兴趣,输出的就是换行符 */ std::sregex_token_iterator it2(str2.begin(), str2.end(), parseReg2, -1); /* 这里取出每一行 */ for(; it2 != std::sregex_token_iterator(); ++it2) { /* 去掉多余的空格 */ auto line = std::regex_replace(it2->str(), parseRegSpace, " "); // LOG_INFO("{}", line); CF_FileInfo fi; /* 取出文件类型 */ std::string strFileType = std::regex_replace(line, parseReg1, "$1"); if(std::regex_match(strFileType, parseReg3)) { fi.type = CF_FileType::DIR; }else if (std::regex_match(strFileType, parseReg4)) { fi.type = CF_FileType::FILE; } /* 取出文件大小 */ std::string strFileSize = std::regex_replace(line, parseReg1, "$5"); fi.size = std::stoull(strFileSize); /* 取出文件名 */ std::string strFileName = std::regex_replace(line, parseReg1, "$7"); fi.name = strFileName; /* 去掉本地目录和上一级目录 */ if(strFileName == "." || strFileName == "..") { continue; } /* 加入队列 */ fileInfo.push_back(fi); } }StdCatch return true; } /* 辅助:拆分绝对路径为 父目录(以/结尾) + 基名 */ static void splitParentAndBasePath(const std::string& absPath, std::string& parentDir, std::string& fileName) { auto pos = absPath.find_last_of('/'); if(pos == std::string::npos) { parentDir = "/"; fileName = absPath; return; } parentDir = absPath.substr(0, pos + 1); fileName = absPath.substr(pos + 1); if(parentDir.empty()) parentDir = "/"; } /* 去掉最后的“/” */ std::string trimTrailingSlash(std::string s) { if (!s.empty() && s.back() == '/') s.pop_back(); return s; } /* ================================================================================== * *********************************** 成员函数 ************************************* * ================================================================================== */ CurlFtp::CurlFtp() { /* 调用初始化函数,这个函数可以重复调用 */ curl_global_init(CURL_GLOBAL_DEFAULT); /* 初始化curl */ hasInstace = true; } CurlFtp::~CurlFtp() { /* 清理curl */ curl_easy_cleanup(m_curl); curl_global_cleanup(); hasInstace = false; } /* 设置用户名和密码 */ bool CurlFtp::setUsernameAndPassword(const std::string& username, const std::string& password) { m_username = username; m_password = password; return true; } /* 设置IP和端口 */ bool CurlFtp::setFtpIPAndPort(const std::string& IP, const int port) { /*先判断是否有ftp器前缀,通过正则表达式,取出IP地址和端口号,去掉前缀和后面的'/ */ m_IP = IP; m_port = port; m_ftpUrl = "ftp://" + m_IP + ":" + std::to_string(m_port); LOG_INFO("Set ftpUrl = " << m_ftpUrl); m_isSftp = false; return true; } /* 设置SFTP的IP和端口 */ bool CurlFtp::setSftpIPAndPort(const std::string& IP, const int port) { m_IP = IP; m_port = port; m_ftpUrl = "sftp://" + m_IP + ":" + std::to_string(m_port); LOG_INFO("Set sftpUrl = " << m_ftpUrl); m_isSftp = true; return true; } /* 设置SFTP密钥,仅对SFTP生效 */ bool CurlFtp::setSftpPrivateKey(const std::string& privateKeyFile, const std::string& publicKeyFile, const std::string& passphrase) { m_sftpPrivateKeyFile = privateKeyFile; m_sftpPublicKeyFile = publicKeyFile; m_sftpPassphrase = passphrase; return true; } /* 设置是否启用CURL的调试信息 */ void CurlFtp::enableCurlDebug(bool isPrint) { m_enableCurlDebug = isPrint; } /* 获取列表,包括文件和文件夹 */ bool CurlFtp::getList(std::string dir, std::vector& fileInfoList) { if(m_IP.empty()) { LOG_WARN("IP or port is empty"); return false; } /* 获取文件信息 */ listByWildcard(dir, fileInfoList); // curl_easy_cleanup(curl); return true; } /* 列出文件列表 */ bool CurlFtp::getFileList(std::string dir, std::vector& fileList) { if(m_IP.empty()) { LOG_WARN("IP or port is empty"); return false; } /* 检查dir,添加前缀“/” */ auto dirTmp = checkDirPath(dir); std::vector listInfo; /* 获取文件信息 */ listByWildcard(dirTmp, listInfo); for(const CF_FileInfo& fi : listInfo) { // LOG_INFO("type = {}, size = {}, name = {}", (int)fi.type, fi.size, fi.name); if(fi.type == CF_FileType::FILE) { fileList.push_back(fi.name); } } // curl_easy_cleanup(curl); return true; } /* 获取文件夹列表 */ bool CurlFtp::getDirList(std::string dir, std::vector& dirList) { if(m_IP.empty()) { LOG_WARN("IP or port is empty"); return false; } /* 检查dir,添加前缀“/” */ auto dirTmp = checkDirPath(dir); std::vector listInfo; /* 获取文件信息 */ listByWildcard(dirTmp, listInfo); for(const CF_FileInfo& fi : listInfo) { // LOG_INFO("type = {}, size = {}, name = {}", (int)fi.type, fi.size, fi.name); if(fi.type == CF_FileType::DIR) { dirList.push_back(fi.name); } } // curl_easy_cleanup(curl); return true; } /** * @brief 判断文件夹是否存在,FTP和SFTP判断方式不同 * FTP使用curl检测判断,但是SFTP无论文件夹是否存在都会返回真,所以只能通过列出文件夹的方式判断 * * @param dir * @return true * @return false */ bool CurlFtp::existsDir(const std::string& dir) { LOG_DEBUG("Check remote dir:" << dir); if(m_ftpUrl.empty()) { LOG_ERROR("ftpUrl is empty"); return false; } if(dir == "/") { return true; } return checkFtpDirExist(dir); } /* 创建FTP文件夹,递归创建 */ bool CurlFtp::createDirectory(const std::string& ftpDir) { /* 检查路径格式,并去掉第一个 / */ std::string ftpDirTmp = checkDirPath(ftpDir); std::string ftpDir2 = ftpDirTmp.substr(1, ftpDirTmp.size() -1); /* 将文件夹分开,取出每一个文件夹名称 */ std::vector strList; std::regex reg(R"(/)"); std::sregex_token_iterator it(ftpDir2.begin(), ftpDir2.end(), reg, -1); for( ; it != std::sregex_token_iterator(); ++it) { strList.push_back(it->str()); } /* 将每一层拼接起来,逐层递归 */ std::vector dirList; for(const std::string& dir : strList) { std::string dirTmp = "/"; if(!dirList.empty()) { dirTmp = dirList.back(); dirTmp += "/"; } dirTmp += dir; dirList.push_back(dirTmp); } /* 逐层创建 */ for(const std::string& dir : dirList) { // LOG_DEBUG("Create dir: {}", dir); if(!createOneDirectory(dir)) { LOG_ERROR("Failed to create dir: " << dir); return false; } } return true; } /* 删除文件,文件后面不能带有“/”否则会报错 删除方式,先 CWD 到该文件目录,再执行 DELE 删除命令 */ bool CurlFtp::deleteFile(const std::string& remoteFile) { if(m_ftpUrl.empty()) { LOG_ERROR("ftpUrl is empty"); return false; } /* 规范化文件路径(以/开头,且不以/结尾) */ std::string filePath = checkFilePath(remoteFile); if(filePath.empty()) { return false; } std::string deleteCmd; std::string ftpUrl; if(m_isSftp == false) { /* 切到父目录后再删除基名(兼容更多服务器) */ // std::string parentDir, fileName; // splitParentAndBasePath(filePath, parentDir, fileName); // if(fileName.empty()) // { // LOG_ERROR("Delete file base name is empty: " << filePath); // return false; // } // ftpUrl = m_ftpUrl + parentDir; // parentDir 以'/'结尾 // deleteCmd = "DELE " + fileName; // ftp 使用 DELE ftpUrl = m_ftpUrl + "/"; deleteCmd = "DELE " + filePath; }else { /* SFTP需要使用绝对路径删除 */ ftpUrl = m_ftpUrl + "/"; deleteCmd = "rm " + filePath; } if(!setCommonCurlOptions(ftpUrl, 10)) { return false; } struct curl_slist *headerlist = nullptr; headerlist = curl_slist_append(headerlist, deleteCmd.c_str()); curl_easy_setopt(m_curl, CURLOPT_QUOTE, headerlist); curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1L); LOG_DEBUG("Delete remote file: " << filePath); bool ret = performCurl(); curl_slist_free_all(headerlist); return ret; } /* 删除文件夹,递归删除 */ bool CurlFtp::deleteDirectory(const std::string& ftpDir) { if(m_ftpUrl.empty()) { LOG_ERROR("ftpUrl is empty"); return false; } /* 规范化目录路径,并去掉末尾斜杠 */ std::string dirWithSlash = checkDirPath(ftpDir); std::string dirNoSlash = trimTrailingSlash(dirWithSlash); if(dirNoSlash.empty() || dirNoSlash == "/") { LOG_ERROR("Refuse to delete root dir: " << dirNoSlash); return false; } /* 递归删除:先删除内部所有文件和子目录,再删除自身 */ std::vector vecChildren; // 使用通配符列举该目录下的所有条目 if(!listByWildcard(dirWithSlash, vecChildren)) { LOG_WARN("List children failed before delete: " << dirWithSlash); } /* 删除文件, 先切换到该目录,避免FTP频繁切换目录 */ if(!changeToDir(dirWithSlash)) { LOG_ERROR("ChangeToDir failed: " << dirWithSlash); return false; } for(const auto& fi : vecChildren) { /* 软连接这些也用文件删除,不过目前貌似无法获取到软连接 */ if(fi.type != CF_FileType::DIR) { if(fi.name.empty() || fi.name == "." || fi.name == "..") { continue; } if(m_isSftp == false) { if(!deleteFileRelative(fi.name)) { return false; } }else { /* SFTP使用绝对路径删除 */ std::string absFile = dirWithSlash + fi.name; if(!deleteFile(absFile)) { return false; } } } } // 再递归删子目录 for(const auto& fi : vecChildren) { if(fi.type == CF_FileType::DIR) { if(fi.name.empty() || fi.name == "." || fi.name == "..") { continue; } std::string childDir = dirWithSlash + fi.name; std::string childNorm = checkDirPath(childDir); if(childNorm == dirWithSlash) { LOG_WARN("Skip self-recursion: " << childNorm); continue; } if(!deleteDirectory(childNorm)) { LOG_ERROR("Failed to delete subdir: " << childDir); return false; } } } /* 删除自身目录,走到这一步,这个文件夹内的所有文件都被删除了 */ struct curl_slist *headerlist = nullptr; std::string deleteCmd; if(m_isSftp == false) { std::string parentDir, baseName; splitParentAndBasePath(dirNoSlash, parentDir, baseName); if(baseName.empty()) { LOG_ERROR("Delete directory base name is empty: " << dirNoSlash); return false; } /* 切换到父文件夹并删除当前文件夹 */ if(!changeToDir(parentDir)) { LOG_ERROR("ChangeToDir parent failed: " << parentDir); return false; } // 相对删除:要求已通过 changeToDir() 进入父目录 if(baseName.empty()) { LOG_ERROR("Delete relative dir name is empty"); return false; } // 不允许包含路径分隔符 if(baseName.find('/') != std::string::npos) { LOG_ERROR("DeleteDirectoryRelative only accepts basename without '/' : " << baseName); return false; } deleteCmd = "RMD " + baseName; // ftp 使用 RMD }else { /* SFTP使用绝对路径删除 */ std::string rootUrl = m_ftpUrl + "/"; if(!setCommonCurlOptions(rootUrl, 10)) { return false; } deleteCmd = std::string("rmdir ") + dirNoSlash; } headerlist = curl_slist_append(headerlist, deleteCmd.c_str()); curl_easy_setopt(m_curl, CURLOPT_QUOTE, headerlist); curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1L); LOG_DEBUG("Delete relative directory: " << dirNoSlash); bool ret = performCurl(); curl_slist_free_all(headerlist); return ret; } /** * @brief 下载文件 * * @param remoteFile * @param localFile * @param timeout 超时时间,单位秒 * @return true * @return false */ bool CurlFtp::downloadFile(const std::string& remoteFile, const std::string& localFile, size_t timeout) { if(m_ftpUrl.empty()) { LOG_ERROR("ftpUrl is empty"); return false; } /* 检查传入的文件是否符合规范 */ std::string remoteFileTmp = checkFilePath(remoteFile); std::string ftpUrl = m_ftpUrl + remoteFileTmp; /* 检查本地文件夹是否存在,不存在则创建 */ // std::string localDir = localFile.substr(0, localFile.find_last_of("/")); /* 去掉最后的 / */ std::string localDirTmp = localFile.substr(0, localFile.find_last_of("/")); if(!checkLocalDir(localDirTmp)) { LOG_ERROR("Failed to create local dir: " << localDirTmp); return false; } LOG_DEBUG("远程文件: " << ftpUrl << ", 本地路径: " << localFile); /* 打开文件,Windows需要转换成 std::wstring 和 std::wofstream */ // #if defined(_WIN32) && defined(_MSC_VER) // std::wstring wLocalFile(localFile.begin(), localFile.end()); // std::wofstream ofs; // ofs.open(wLocalFile, std::ios::out | std::ios::binary | std::ios::trunc); // #else // std::ofstream ofs; // ofs.open(localFile, std::ios::out | std::ios::binary | std::ios::trunc); // #endif /* defined(_WIN32) || defined(_WIN64) */ // if(!ofs.is_open()) // { // LOG_ERROR("Failed to open local file: " << localFile); // LOG_ERROR("Error info: " << std::strerror(errno)); // return false; // } QFile file(QString::fromStdString(localFile)); if(!file.open(QIODevice::WriteOnly)) { LOG_ERROR("Failed to open local file: " << localFile); return false; } /* 设置FTP地址 */ setCommonCurlOptions(ftpUrl, timeout); /* 设置回调函数 */ curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeFileCallBack_QT); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &file); /* 设置进度回调函数 */ curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, progress_callback); curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, nullptr); /* 启用下载进度回调函数 */ curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 0L); /* 关闭被动模式,使用主动模式 */ curl_easy_setopt(m_curl, CURLOPT_FTP_USE_EPSV, 0L); /* 使用二进制传输 */ curl_easy_setopt(m_curl, CURLOPT_TRANSFERTEXT, 0L); // 忽略 PASV 返回的地址,使用控制连接的地址,解决 NAT 场景 // curl_easy_setopt(m_curl, CURLOPT_FTP_SKIP_PASV_IP, 1L); /* 发送请求 */ bool ret = performCurl(); if(!ret) { std::stringstream ss; ss << "Failed to download file: " << ftpUrl; LOG_ERROR("Failed to get file list, Url = " << ftpUrl); /* 清理下载失败的文件 */ file.close(); std::remove(localFile.c_str()); return false; } /* 关闭文件,清理curl */ file.close(); // curl_easy_cleanup(curl); /* 打印一个换行符 */ // printf("\n"); printf("\n"); return true; } /* 下载文件到数组 */ bool CurlFtp::downloadToArray(const std::string& remoteFile, std::vector& arrayInfo, size_t timeout) { if(m_ftpUrl.empty()) { LOG_ERROR("ftpUrl is empty"); return false; } /* 检查传入的文件是否符合规范 */ std::string remoteFileTmp = checkFilePath(remoteFile); std::string ftpUrl = m_ftpUrl + remoteFileTmp; /* 设置FTP地址 */ setCommonCurlOptions(ftpUrl, timeout); /* 设置回调函数 */ curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeDataCallBack); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &arrayInfo); /* 设置进度回调函数 */ curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, progress_callback); curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, nullptr); /* 启用下载进度回调函数 */ curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 0L); /* 关闭被动模式,使用主动模式 */ curl_easy_setopt(m_curl, CURLOPT_FTP_USE_EPSV, 0L); /* 使用二进制传输 */ curl_easy_setopt(m_curl, CURLOPT_TRANSFERTEXT, 0L); /* 发送请求 */ bool ret = performCurl(); if(!ret) { LOG_ERROR("Failed to get file list, Url = " << ftpUrl); } /* 清理curl */ // curl_easy_cleanup(curl); /* 打印一个换行符 */ // printf("\n"); printf("\n"); return ret; } /** * @brief 上传文件 * * @param localFile 本地文件 * @param remoteFile 远程文件 * @param timeout 超时时间,单位秒 * @return true * @return false */ bool CurlFtp::uploadFile(const std::string& localFile, const std::string& remoteFile, size_t timeout, bool isCreateDir) { if(m_ftpUrl.empty()) { LOG_ERROR("Url is empty"); return false; } // printf("uploadFile: %d\n", __LINE__); /* 检查本地文件是否存在 */ if(!checkLocalFileExist(localFile)) { LOG_ERROR("Local file is not exist: " << localFile); return false; } // printf("uploadFile: %d\n", __LINE__); /* 检查FTP文件名是否符合规范 */ std::string remoteFileTmp = checkFilePath(remoteFile); std::string remoteDirTmp = remoteFileTmp.substr(0, remoteFileTmp.find_last_of("/")); // printf("uploadFile: %d\n", __LINE__); /* 检查远程FTP上的文件夹是否存在,不存在则创建 */ if(!existsDir(remoteDirTmp)) { if(isCreateDir) { if(!createDirectory(remoteDirTmp)) { // LOG_ERROR("Failed to create remote dir: {}", remoteFileTmp); return false; } }else { LOG_ERROR("Remote dir is not exist: " << remoteDirTmp); return false; } } // printf("uploadFile: %d\n", __LINE__); /* 拼接远程文件的url */ std::string ftpUrl = m_ftpUrl + remoteFileTmp; /* 打开文件 */ std::ifstream ifs; ifs.open(localFile, std::ios::in | std::ios::binary); if(!ifs.is_open()) { LOG_ERROR("Failed to open local file: " << localFile); return false; } // printf("uploadFile: %d\n", __LINE__); /* 获取文件大小 */ // auto startPos = ifs.tellg(); ifs.seekg(0, std::ios::end); auto fileSize = ifs.tellg(); /* 恢复指针到文件头 */ ifs.seekg(0, std::ios::beg); LOG_DEBUG("File size: " << (long)fileSize); /* 设置FTP地址 */ if(!setCommonCurlOptions(ftpUrl, timeout)) { ifs.close(); return false; } /* 启用上传 */ curl_easy_setopt(m_curl, CURLOPT_UPLOAD, 1L); /* 设置回调函数 */ curl_easy_setopt(m_curl, CURLOPT_READFUNCTION, readFileCallBack); curl_easy_setopt(m_curl, CURLOPT_READDATA, &ifs); /* 设置上传文件的大小 */ curl_easy_setopt(m_curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize); /* 设置进度回调函数 */ curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, progress_callback); curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, nullptr); /* 启用下载进度回调函数 */ curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 0L); /* 关闭被动模式,使用主动模式 */ curl_easy_setopt(m_curl, CURLOPT_FTP_USE_EPSV, 0L); /* 使用二进制传输 */ curl_easy_setopt(m_curl, CURLOPT_TRANSFERTEXT, 0L); // printf("uploadFile: %d\n", __LINE__); /* 发送请求 */ bool ret = performCurl(); if(!ret) { LOG_ERROR("Upload file failed, Url = " << ftpUrl); } // printf("uploadFile: %d\n", __LINE__); /* 关闭文件,清理curl */ ifs.close(); // printf("uploadFile: %d\n", __LINE__); // curl_easy_cleanup(curl); /* 打印一个换行符 */ printf("\n"); return ret; } /** * @brief 上传文件,上传数据 * 注意:函数内不会拷贝数据,因此在上传完成前需要保证该指针的有效性,拷贝完成后也不会销毁源数据 * 默认超时时间是30秒,也无法设置 * * @param srcData 数据指针 * @param size 数据大小 * @param remoteFile 远程文件名,包括地址 * @param timeout 超时时间,单位秒 * @return true * @return false */ bool CurlFtp::uploadData(char* srcData, size_t size, const std::string& remoteFile, size_t timeout, bool isCreateDir) { if(m_ftpUrl.empty()) { LOG_ERROR("Url is empty"); return false; } /* 初始化本地数据 */ CF_ArrayInfo arrayInfo; arrayInfo.data = srcData; arrayInfo.size = size; arrayInfo.pos = 0; /* 检查FTP文件名是否符合规范 */ std::string remoteFileTmp = checkFilePath(remoteFile); std::string remoteDirTmp = remoteFileTmp.substr(0, remoteFileTmp.find_last_of("/")); /* 检查远程FTP上的文件夹是否存在,不存在则创建 */ if(!existsDir(remoteDirTmp)) { if(isCreateDir) { if(!createDirectory(remoteDirTmp)) { // LOG_ERROR("Failed to create remote dir: {}", remoteFileTmp); return false; } }else { LOG_ERROR("Remote dir is not exist: " << remoteDirTmp); return false; } } /* 拼接远程文件的url */ std::string ftpUrl = m_ftpUrl + remoteFileTmp; LOG_DEBUG("Data size: " << arrayInfo.size); /* 设置FTP地址 */ if(!setCommonCurlOptions(ftpUrl, timeout)) { return false; } /* 启用上传 */ curl_easy_setopt(m_curl, CURLOPT_UPLOAD, 1L); /* 设置回调函数 */ curl_easy_setopt(m_curl, CURLOPT_READFUNCTION, readDataCallBack); curl_easy_setopt(m_curl, CURLOPT_READDATA, &arrayInfo); /* 设置上传文件的大小 */ curl_easy_setopt(m_curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)arrayInfo.size); /* 设置进度回调函数 */ curl_easy_setopt(m_curl, CURLOPT_XFERINFOFUNCTION, progress_callback); curl_easy_setopt(m_curl, CURLOPT_XFERINFODATA, nullptr); /* 启用下载进度回调函数 */ curl_easy_setopt(m_curl, CURLOPT_NOPROGRESS, 0L); /* 关闭被动模式,使用主动模式 */ curl_easy_setopt(m_curl, CURLOPT_FTP_USE_EPSV, 0L); /* 使用二进制传输 */ curl_easy_setopt(m_curl, CURLOPT_TRANSFERTEXT, 0L); if(m_enableCurlDebug) { /* 启用调试信息 */ curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L); } /* 发送请求 */ bool ret = performCurl(); if(!ret) { LOG_ERROR("Upload file failed, Url = " << ftpUrl); } /* 关闭文件,清理curl */ // curl_easy_cleanup(curl); /* 打印一个换行符 */ printf("\n"); return ret; } /** * @brief 列出文件列表,这个需要的参数很多,无法设置成静态函数 * * @param curl CURL句柄 * @param dir 文件夹,相对路径,不带有IP和端口号 * @param fileList 返回值,文件列表 * @return true * @return false */ // bool CurlFtp::listAll(CURL* curl, std::string dir, std::vector& fileInfoList) // { // if(m_IP.empty()) // { // LOG_ERROR("IP is empty"); // return false; // } // // bool result = false; // std::string strSrc; // /* 先设置FTP地址 */ // std::string ftpUrl = m_ftpUrl + dir; // if(!setCommonCurlOptions(curl, ftpUrl, 10)) // { // return false; // } // /* 设置列出文件命令,只列出文件名称,不携带信息 */ // // curl_easy_setopt(m_curl, CURLOPT_DIRLISTONLY, 1L); // /* 设置为文本传输 */ // curl_easy_setopt(curl, CURLOPT_TRANSFERTEXT, 1L); // /* 设置回调函数 */ // curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback); // /* 设置需要写入的容器,回调函数的第四个参数 */ // curl_easy_setopt(curl, CURLOPT_WRITEDATA, &strSrc); // // 禁用被动模式,设置为0是禁用(如果需要) // // curl_easy_setopt(curl, CURLOPT_FTP_USE_EPSV, 1L); // /* 发送请求 */ // // CURLcode res = curl_easy_perform(curl); // bool ret = performCurl(curl); // if(!ret) // { // LOG_ERROR("Failed to get file listUrl = " << ftpUrl); // return false; // } // /* 解析字符串 */ // parseFileInfo(strSrc, fileInfoList); // return true; // } /** * @brief 列出文件列表,这个需要的参数很多,无法设置成静态函数 * * @param curl CURL句柄 * @param dir 文件夹,相对路径,不带有IP和端口号 * @param fileList 返回值,文件列表 * @return true * @return false */ bool CurlFtp::listByWildcard(const std::string& dir, std::vector& out) { if(m_IP.empty()) { LOG_ERROR("IP is empty"); return false; } /* 规范化目录并追加通配符 */ std::string dirWithSlash = checkDirPath(dir); // 保证以'/'结束 std::string ftpUrl; if(m_isSftp) { ftpUrl = m_ftpUrl + dirWithSlash; }else { ftpUrl = m_ftpUrl + dirWithSlash + "*"; } if(!setCommonCurlOptions(ftpUrl, 10)) { return false; } /* 启用通配符匹配并注册回调,仅收集信息不下载 */ if(m_isSftp == false) { /* 设置为文本传输 */ curl_easy_setopt(m_curl, CURLOPT_TRANSFERTEXT, 1L); curl_easy_setopt(m_curl, CURLOPT_WILDCARDMATCH, 1L); curl_easy_setopt(m_curl, CURLOPT_CHUNK_BGN_FUNCTION, chunk_bgn_cb); curl_easy_setopt(m_curl, CURLOPT_CHUNK_END_FUNCTION, chunk_end_cb); curl_easy_setopt(m_curl, CURLOPT_CHUNK_DATA, &out); bool ret = performCurl(); if(!ret) { LOG_ERROR("Wildcard list failed, Url = " << ftpUrl); } return ret; }else { /* 设置回调函数 */ std::string strSrc; curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, writeStringCallback); /* 设置需要写入的容器,回调函数的第四个参数 */ curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &strSrc); /* 发送请求 */ bool ret = performCurl(); if(!ret) { LOG_ERROR("Failed to get file list, Url = " << ftpUrl); return false; } /* 解析字符串 */ parseFileInfo(strSrc, out); return true; } } /* 设置常规通用的curl设置 */ bool CurlFtp::setCommonCurlOptions(const std::string& ftpUrl, size_t timeout) { if(!resetCurl()) { return false; } /* 设置FTP地址 */ curl_easy_setopt(m_curl, CURLOPT_URL, ftpUrl.c_str()); curl_easy_setopt(m_curl, CURLOPT_PORT, m_port); /* 设置用户名和密码 */ curl_easy_setopt(m_curl, CURLOPT_USERNAME, m_username.c_str()); curl_easy_setopt(m_curl, CURLOPT_PASSWORD, m_password.c_str()); curl_easy_setopt(m_curl, CURLOPT_USERPWD, (m_username + ":" + m_password).c_str()); /* 启用跟随重定向 */ curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1L); /* 设置超时时间 */ curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, timeout); /* 启用持久连接 */ curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L); if(m_enableCurlDebug) { /* 启用调试信息 */ curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L); } /* 设置SFTP */ if(!setSftp()) { return false; } return true; } /* 检查文件夹路径是否合规,不合规就修改 */ std::string CurlFtp::checkDirPath(const std::string& dir) { std::string dirTmp = dir; /* 检查是否以“/”开头 */ std::regex reg(R"(^/[.]*)"); if(!std::regex_match(dirTmp, reg)) { dirTmp = "/" + dirTmp; } /* 如果有重复的“/”,替换成一个 “/” */ std::regex reg1(R"(//)"); dirTmp = std::regex_replace(dirTmp, reg1, "/"); /* 检查是否以“/”结束 */ if (dirTmp.back() != '/') { dirTmp.push_back('/'); } return dirTmp; } /* 检查文件路径是否合规 */ std::string CurlFtp::checkFilePath(const std::string& file) { std::string dirTmp = file; /* 检查是否以“/”结束,如果是以“/”结尾,返回空字符串,并报错 */ std::regex reg2(R"([.]*/$)"); if(std::regex_match(dirTmp, reg2)) { LOG_ERROR("File path is not correct, end with '/'"); return std::string(); } /* 检查是否以“/”开头 */ std::regex reg(R"(^/[.]*)"); if(!std::regex_match(dirTmp, reg)) { dirTmp = "/" + dirTmp; } /* 如果有重复的“/”,替换成一个 “/” */ std::regex reg1(R"(//)"); dirTmp = std::regex_replace(dirTmp, reg1, "/"); return dirTmp; } /** * @brief 检查本地文件夹是否存在,不存在则创建 * 这里默认传入的是文件夹路径,不是文件路径,如果最后有‘/’,会自动去掉 * * @param localDir 文件夹路径 * @return true * @return false */ bool CurlFtp::checkLocalDir(const std::string& localDir) { /* 去掉最后的‘/’ */ std::regex reg(R"([.]*/$)"); std::string localDirTmp = std::regex_replace(localDir, reg, ""); /* 检查文件夹是否存在 */ // #if (__cplusplus >= 201703L) // if(std::filesystem::exists(localDirTmp)) // #else if(QDir(localDirTmp.c_str()).exists()) // #endif /* (__cplusplus >= 201703L) */ { return true; } /* 创建文件夹 */ // #if (__cplusplus >= 201703L) // if(!std::filesystem::create_directories(localDirTmp)) // #else if(!QDir().mkpath(localDirTmp.c_str())) // #endif /* (__cplusplus >= 201703L) */ { LOG_ERROR("Failed to create local dir: " << localDirTmp); return false; } return true; } /* 检查本地文件夹是否存在,不会创建 */ bool CurlFtp::checkLocalDirExist(const std::string& localDir) { /* 去掉最后的‘/’ */ std::regex reg(R"([.]*/$)"); std::string localDirTmp = std::regex_replace(localDir, reg, ""); /* 检查文件夹是否存在 */ bool result = false; // #if (__cplusplus >= 201703L) // result = std::filesystem::exists(localDirTmp); // #else result = QDir(localDirTmp.c_str()).exists(); // #endif /* (__cplusplus >= 201703L) */ return result; } /* 检查本地文件是否存在 */ bool CurlFtp::checkLocalFileExist(const std::string& localFile) { /* 去掉最后的‘/’ */ // std::regex reg(R"([.]*/$)"); std::string localDirTmp = trimTrailingSlash(localFile); /* 检查文件是否存在 */ bool result = false; // #if (__cplusplus >= 201703L) // result = std::filesystem::exists(localDirTmp); // #else result = QFile(localDirTmp.c_str()).exists(); // #endif /* (__cplusplus >= 201703L) */ return result; } /* 执行curl,添加重试机制 */ bool CurlFtp::performCurl() { int retry = 1; while(retry > 0) { CURLcode res = curl_easy_perform(m_curl); if(res == CURLE_OK) { return true; } else if (res == CURLE_LOGIN_DENIED) { LOG_ERROR("Login failed, error code: " << (int)res << ", " << curl_easy_strerror(res)); /* 设置用户名和密码 */ curl_easy_setopt(m_curl, CURLOPT_USERNAME, m_username.c_str()); curl_easy_setopt(m_curl, CURLOPT_PASSWORD, m_password.c_str()); } else if(res == CURLE_REMOTE_FILE_NOT_FOUND) { LOG_WARN("Remote file or directory not found, error code: " << (int)res << ", " << curl_easy_strerror(res)); return false; } else { LOG_ERROR("Perform curl failed, error code: " << (int)res << ", " << curl_easy_strerror(res)); LOG_ERROR("Retry times: " << (4 - retry)); } retry--; } return false; } bool CurlFtp::performCurlStrict() { int retry = 1; while (retry-- > 0) { CURLcode res = curl_easy_perform(m_curl); if (res == CURLE_OK) return true; LOG_ERROR("Strict perform failed, code: " << (int)res << ", " << curl_easy_strerror(res)); } return false; } /* 重置curl设置 */ bool CurlFtp::resetCurl() { if(m_curl == nullptr) { m_curl = curl_easy_init(); if(m_curl == nullptr) { LOG_ERROR("curl init failed !"); return false; } }else { curl_easy_reset(m_curl); } return true; } /* 设置sftp,如果是sftp就设置 */ bool CurlFtp::setSftp() { /* 判断是否是SFTP */ if(m_isSftp == false) { return true; } /* 设置仅使用密码验证,不优先验证密钥 */ if(m_isSftpEnableKey == false) { curl_easy_setopt(m_curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PASSWORD); } else { /* 设置使用密钥验证 */ curl_easy_setopt(m_curl, CURLOPT_SSH_AUTH_TYPES, CURLSSH_AUTH_PUBLICKEY); /* 设置公钥和私钥路径 */ curl_easy_setopt(m_curl, CURLOPT_SSH_PUBLIC_KEYFILE, m_sftpPublicKeyFile.c_str()); curl_easy_setopt(m_curl, CURLOPT_SSH_PRIVATE_KEYFILE, m_sftpPrivateKeyFile.c_str()); /* 如果密钥有密码,则设置密码 */ if(!m_sftpPassphrase.empty()) { curl_easy_setopt(m_curl, CURLOPT_KEYPASSWD, m_sftpPassphrase.c_str()); } } return true; } /** * @brief 检查FTP文件夹是否存在,注意,传入的文件夹最后一定要带/,否则会检查的是文件 * 现在检测方式和SFTP一致,都使用列出上一层目录的方式进行检测 * * @param dir * @return true * @return false */ bool CurlFtp::checkFtpDirExist(const std::string& dir) { /* 检查传入的文件夹 */ auto dirTmp = checkDirPath(dir); /* 获取文件夹 */ std::string parentDir = QFileInfo(QString::fromStdString(dir)).path().toStdString(); std::vector vecDir; bool ret = listByWildcard(parentDir, vecDir); if(!ret) { LOG_ERROR("Failed to check ftp dir: " << dir); return false; } /* 取出本层文件夹名称 */ std::string dirName = QFileInfo(QString::fromStdString(dir)).fileName().toStdString(); /* 判断是否存在 */ bool result = false; for(const auto& fi : vecDir) { if(fi.name == dirName && fi.type == CF_FileType::DIR) { result = true; break; } } return result; } /* 检查SFTP文件夹是否存在,这里通过列出上一层的文件夹中的内容,然后进行对比判断的 */ // bool CurlFtp::checkSftpDirExist(const std::string& dir) // { // /* 取出上一层文件夹路径 */ // std::string parentDir = QFileInfo(QString::fromStdString(dir)).path().toStdString(); // std::vector vecDir; // bool ret = getDirList(parentDir, vecDir); // if(!ret) // { // LOG_ERROR("Failed to check sftp dir: " << dir); // return false; // } // /* 取出本层文件夹名称 */ // std::string dirName = QFileInfo(QString::fromStdString(dir)).fileName().toStdString(); // /* 判断是否存在 */ // bool result = false; // for(const std::string& str : vecDir) // { // if(str == dirName) // { // result = true; // break; // } // } // return result; // } /* 检查FTP/SFTP文件是否存在,使用通配符获取文件列表匹配 */ bool CurlFtp::checkFtpFileExist(const std::string& file) { /* 判断文件字符是否符合规范 */ auto fileTmp = checkFilePath(file); if(fileTmp.empty()) { LOG_ERROR("File path is not correct: " << file); return false; } /* 取出上一层文件夹路径 */ std::string parentDir = QFileInfo(QString::fromStdString(file)).path().toStdString(); std::vector vecFiles; bool ret = listByWildcard(parentDir, vecFiles); if(!ret) { LOG_ERROR("Failed to check ftp file: " << file); return false; } /* 取出本层文件名称 */ std::string fileName = QFileInfo(QString::fromStdString(file)).fileName().toStdString(); /* 判断是否存在 */ bool result = false; for(const auto& fi : vecFiles) { if(fi.name == fileName && fi.type != CF_FileType::DIR) { result = true; break; } } return result; } /* 进入一个路径 */ bool CurlFtp::changeToDir(const std::string& dir) { if(m_ftpUrl.empty()) { LOG_ERROR("ftpUrl is empty"); return false; } // 规范化目录,确保以'/'结尾 std::string dirWithSlash = checkDirPath(dir); std::string url = m_ftpUrl + dirWithSlash; // 设置到该目录并发送一个不传输实体的请求,相当于验证可进入 if(!setCommonCurlOptions(url, 10)) { return false; } curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1L); LOG_DEBUG("Change to remote dir: " << dirWithSlash); return performCurlStrict(); } /* 删除一个文件,相对路径,调用这个函数,需要先调用上面那个函数进入路径 */ bool CurlFtp::deleteFileRelative(const std::string& remoteFile) { // 相对删除:要求已通过 changeToDir() 进入父目录 // 仅接受基名,不允许包含'/',且不允许以'/'结尾 if(remoteFile.empty()) { LOG_ERROR("Delete relative file name is empty"); return false; } // 不允许包含路径分隔符 if(remoteFile.find('/') != std::string::npos) { LOG_ERROR("DeleteFileRelative only accepts basename without '/' : " << remoteFile); return false; } // 当前 m_curl 已经被 changeToDir() 设置到父目录 URL // 直接下发 DELE/rm 命令 struct curl_slist *headerlist = nullptr; std::string deleteCmd; if(m_isSftp) { deleteCmd = "rm " + remoteFile; // sftp 使用 rm } else { deleteCmd = "DELE " + remoteFile; // ftp 使用 DELE } headerlist = curl_slist_append(headerlist, deleteCmd.c_str()); curl_easy_setopt(m_curl, CURLOPT_QUOTE, headerlist); curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1L); LOG_DEBUG("Delete relative file: " << remoteFile); bool ret = performCurl(); curl_slist_free_all(headerlist); return ret; } /* 删除一个文件夹,相对路径,调用这个函数,需要先调用上面那个函数进入路径 */ bool CurlFtp::deleteDirectoryRelative(const std::string& ftpDir) { // 相对删除:要求已通过 changeToDir() 进入父目录 // 仅接受基名,不允许包含'/',且不允许以'/'结尾 if(ftpDir.empty()) { LOG_ERROR("Delete relative dir name is empty"); return false; } // 不允许包含路径分隔符 if(ftpDir.find('/') != std::string::npos) { LOG_ERROR("DeleteDirectoryRelative only accepts basename without '/' : " << ftpDir); return false; } // 当前 m_curl 已经被 changeToDir() 设置到父目录 URL // 直接下发 RMD/rmdir 命令 struct curl_slist *headerlist = nullptr; std::string deleteCmd; if(m_isSftp) { deleteCmd = "rmdir " + ftpDir; // sftp 使用 rmdir } else { deleteCmd = "RMD " + ftpDir; // ftp 使用 RMD } headerlist = curl_slist_append(headerlist, deleteCmd.c_str()); curl_easy_setopt(m_curl, CURLOPT_QUOTE, headerlist); curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1L); LOG_DEBUG("Delete relative directory: " << ftpDir); bool ret = performCurl(); curl_slist_free_all(headerlist); return ret; } /** * @brief 创建FTP文件夹,只能创建一层文件夹 * * @param ftpDir 文件夹路径 * @return true 文件夹创建成功或者文件夹存在 * @return false */ bool CurlFtp::createOneDirectory(const std::string& ftpDir) { if(m_ftpUrl.empty()) { LOG_ERROR("ftpUrl is empty"); return false; } /* 先检查FTP文件夹是否存在,如果存在直接返回true */ if(existsDir(ftpDir)) { return true; } setCommonCurlOptions(m_ftpUrl, 30); /* 检查传入的文件夹格式 */ auto dirTmp = checkDirPath(ftpDir); /* 去掉末尾的“/” */ auto dirNoSlash = trimTrailingSlash(dirTmp); /* 创建FTP头信息 */ struct curl_slist *headerlist = NULL; std::string mkdir; if(m_isSftp) { mkdir = "mkdir " + dirNoSlash; }else { mkdir = "MKD " + dirNoSlash; } headerlist = curl_slist_append(headerlist, mkdir.c_str()); /* 设置FTP命令行选项 */ curl_easy_setopt(m_curl, CURLOPT_QUOTE, headerlist); /* 不包含实体 */ curl_easy_setopt(m_curl, CURLOPT_NOBODY, 1L); LOG_DEBUG("Create remote dir: " << dirTmp); // CURLcode res = curl_easy_perform(curl); bool ret = performCurl(); if(!ret) { LOG_ERROR("Failed to create remote Dir"); } // curl_easy_cleanup(curl); curl_slist_free_all(headerlist); return ret; }