#include "CurlFtp.h" #include #include #include #include "stdlog.h" #if (__cplusplus >= 201703L) #include #else #include #include #endif /* (__cplusplus >= 201703L) */ #if defined(_WIN32) #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 写入数据到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 : %ldB / %ldB, %.2f%%, Speed:%.2f %s\r", transType.c_str(), total, 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; } /* 使用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) { #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"(^-.*$)"); /* -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; /* 加入队列 */ fileInfo.push_back(fi); } return true; } /* ================================================================================== * *********************************** 成员函数 ************************************* * ================================================================================== */ 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; } /* 设置是否忽略SSL证书,使用SFTP不建议忽略 */ void CurlFtp::setIgnoreSSLCert(bool isIgnore) { m_isIgnoreSSLCert = isIgnore; } /* 设置CA证书文件 */ void CurlFtp::setCaCertFile(const std::string& caCertFile) { m_caCertFile = caCertFile; } /* 设置是否启用CURL的调试信息 */ void CurlFtp::enableCurlDebug(bool isPrint) { m_enableCurlDebug = isPrint; } /* 列出文件列表 */ 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); // CURL *curl = nullptr; // curl = curl_easy_init(); // if(curl == nullptr) // { // LOG_ERROR("curl init failed !"); // return false; // } resetCurl(m_curl); std::vector listInfo; /* 获取文件信息 */ listAll(m_curl, 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); // CURL *curl = nullptr; // curl = curl_easy_init(); // if(curl == nullptr) // { // LOG_ERROR("curl init failed !"); // return false; // } resetCurl(m_curl); std::vector listInfo; /* 获取文件信息 */ listAll(m_curl, 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::isDirExist(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; } bool result = false; if(m_isSftp) { result = checkSftpDirExist(dir); }else { result = checkFtpDirExist(dir); } return result; } /** * @brief 创建FTP文件夹,只能创建一层文件夹 * * @param ftpDir 文件夹路径 * @return true 文件夹创建成功或者文件夹存在 * @return false */ bool CurlFtp::createDirectory(const std::string& ftpDir) { if(m_ftpUrl.empty()) { LOG_ERROR("ftpUrl is empty"); return false; } /* 先检查FTP文件夹是否存在,如果存在直接返回true */ if(isDirExist(ftpDir)) { return true; } /* 检查传入的文件夹格式 */ auto dirTmp = checkDirPath(ftpDir); // CURL *curl = curl_easy_init(); // if(curl == nullptr) // { // LOG_ERROR("Create FTP DIR, curl init failed !"); // return false; // } resetCurl(m_curl); curl_easy_setopt(m_curl, CURLOPT_URL, m_ftpUrl.c_str()); curl_easy_setopt(m_curl, CURLOPT_USERNAME, m_username.c_str()); curl_easy_setopt(m_curl, CURLOPT_PASSWORD, m_password.c_str()); /* 创建FTP头信息 */ struct curl_slist *headerlist = NULL; std::string mkdir; if(m_isSftp) { mkdir = "MKDIR " + dirTmp; }else { mkdir = "MKD " + dirTmp; } 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); /* 启用跟随重定向 */ curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1L); /* 设置超时时间 */ curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, 30L); curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, 30L); /* 设置SFTP */ setSftp(m_curl); if(m_enableCurlDebug) { /* 启用调试信息 */ curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L); } // 启用持久连接 // curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L); LOG_DEBUG("Create remote dir: " << dirTmp); // CURLcode res = curl_easy_perform(curl); bool ret = performCurl(m_curl); if(!ret) { LOG_ERROR("Failed to create remote Dir"); } // curl_easy_cleanup(curl); curl_slist_free_all(headerlist); return ret; } /* 创建FTP文件夹,递归创建 */ bool CurlFtp::createDirectories(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(!createDirectory(dir)) { LOG_ERROR("Failed to create dir: " << dir); return false; } } return true; } /** * @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; } // CURL* curl = nullptr; // curl = curl_easy_init(); // if(curl == nullptr) // { // LOG_ERROR("curl init failed !"); // return false; // } resetCurl(m_curl); /* 打开文件 */ std::ofstream ofs; ofs.open(localFile, std::ios::out | std::ios::binary | std::ios::trunc); /* 设置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_WRITEFUNCTION, writeFileCallBack); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &ofs); /* 设置超时时间 */ curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, timeout); /* 设置进度回调函数 */ 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); /* 设置SFTP */ setSftp(m_curl); if(m_enableCurlDebug) { /* 启用调试信息 */ curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L); } /* 启用持久连接 */ curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L); /* 发送请求 */ bool ret = performCurl(m_curl); if(!ret) { std::stringstream ss; ss << "Failed to download file: " << ftpUrl; LOG_ERROR("Failed to get file list, Url = " << ftpUrl); /* 清理下载失败的文件 */ ofs.close(); std::remove(localFile.c_str()); return false; } /* 关闭文件,清理curl */ ofs.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; // CURL* curl = nullptr; // curl = curl_easy_init(); // if(curl == nullptr) // { // LOG_ERROR("curl init failed !"); // return false; // } resetCurl(m_curl); /* 设置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_WRITEFUNCTION, writeDataCallBack); curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &arrayInfo); /* 设置超时时间 */ curl_easy_setopt(m_curl, CURLOPT_TIMEOUT, timeout); curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, timeout); /* 设置进度回调函数 */ 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); /* 设置SFTP */ setSftp(m_curl); if(m_enableCurlDebug) { /* 启用调试信息 */ curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L); } /* 启用持久连接 */ curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L); /* 发送请求 */ bool ret = performCurl(m_curl); 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(!isDirExist(remoteDirTmp)) { if(isCreateDir) { if(!createDirectories(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); // CURL* curl = nullptr; // curl = curl_easy_init(); // if(curl == nullptr) // { // LOG_ERROR("curl init failed !"); // ifs.close(); // return false; // } if(!resetCurl(m_curl)) { ifs.close(); return false; } // printf("uploadFile: %d\n", __LINE__); /* 设置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_FOLLOWLOCATION, 1L); /* 启用上传 */ 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_TIMEOUT, timeout); curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, timeout); /* 设置进度回调函数 */ 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); /* 设置SFTP */ setSftp(m_curl); /* 启用调试信息 */ if(m_enableCurlDebug) { curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L); } /* 启用持久连接 */ curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L); // printf("uploadFile: %d\n", __LINE__); /* 发送请求 */ bool ret = performCurl(m_curl); 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(!isDirExist(remoteDirTmp)) { if(isCreateDir) { if(!createDirectories(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); // CURL* curl = nullptr; // curl = curl_easy_init(); // if(curl == nullptr) // { // LOG_ERROR("curl init failed !"); // return false; // } if(!resetCurl(m_curl)) { 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(curl, CURLOPT_USERNAME, m_username.c_str()); // curl_easy_setopt(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_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_TIMEOUT, timeout); curl_easy_setopt(m_curl, CURLOPT_CONNECTTIMEOUT, timeout); /* 设置进度回调函数 */ 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, 1L); if(m_enableCurlDebug) { /* 启用调试信息 */ curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L); } /* 启用持久连接 */ curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L); /* 发送请求 */ bool ret = performCurl(m_curl); 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; curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str()); curl_easy_setopt(curl, CURLOPT_PORT, m_port); /* 设置用户名和密码 */ curl_easy_setopt(curl, CURLOPT_USERNAME, m_username.c_str()); curl_easy_setopt(curl, CURLOPT_PASSWORD, m_password.c_str()); /* 设置列出文件命令,只列出文件名称,不携带信息 */ // curl_easy_setopt(m_curl, CURLOPT_DIRLISTONLY, 1L); /* 设置回调函数 */ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback); /* 设置需要写入的容器,回调函数的第四个参数 */ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &strSrc); /* 设置超时时间 */ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L); curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 10L); // 禁用被动模式,设置为0是禁用(如果需要) // curl_easy_setopt(curl, CURLOPT_FTP_USE_EPSV, 1L); if(m_enableCurlDebug) { /* 启用调试信息 */ curl_easy_setopt(curl, CURLOPT_VERBOSE, 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; } /* 检查文件夹路径是否合规,不合规就修改 */ 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, "/"); /* 检查是否以“/”结束 */ std::regex reg2(R"([.]*/$)"); if(!std::regex_match(dirTmp, reg2)) { dirTmp = dirTmp + "/"; } 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 = std::regex_replace(localFile, reg, ""); /* 检查文件是否存在 */ bool result = false; #if (__cplusplus >= 201703L) result = std::filesystem::exists(localFile); #else result = QFile(localFile.c_str()).exists(); #endif /* (__cplusplus >= 201703L) */ return result; } /* 执行curl,添加重试机制 */ bool CurlFtp::performCurl(CURL* curl) { int retry = 3; while(retry > 0) { CURLcode res = curl_easy_perform(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(curl, CURLOPT_USERNAME, m_username.c_str()); curl_easy_setopt(curl, CURLOPT_PASSWORD, m_password.c_str()); } else { LOG_ERROR("Perform curl failed, error code: " << (int)res << ", " << curl_easy_strerror(res)); LOG_ERROR("Retry times: " << (4 - retry)); } retry--; } return false; } /* 重置curl设置 */ bool CurlFtp::resetCurl(CURL* curl) { 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(CURL* curl) { /* 判断是否是SFTP */ if(m_isSftp) { /* 判断是否需要设置CA证书 */ if(m_isIgnoreSSLCert) { /* 忽略证书验证 */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); /* 忽略主机名验证 */ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); } else { /* 设置CA证书 */ curl_easy_setopt(curl, CURLOPT_CAINFO, m_caCertFile.c_str()); } } return true; } /** * @brief 检查FTP文件夹是否存在,注意,传入的文件夹最后一定要带/,否则会检查的是文件 * * @param dir * @return true * @return false */ bool CurlFtp::checkFtpDirExist(const std::string& dir) { /* 检查传入的文件夹 */ auto dirTmp = checkDirPath(dir); resetCurl(m_curl); std::string ftpUrl = m_ftpUrl + dirTmp; curl_easy_setopt(m_curl, CURLOPT_URL, ftpUrl.c_str()); 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_NOBODY, 1L); // curl_easy_setopt(m_curl, CURLOPT_HEADER, 1L); /* 设置SFTP */ setSftp(m_curl); /* 启用调试信息 */ if(m_enableCurlDebug) { curl_easy_setopt(m_curl, CURLOPT_VERBOSE, 1L); } /* 启用持久连接 */ // curl_easy_setopt(m_curl, CURLOPT_TCP_KEEPALIVE, 1L); /* 这里只需要执行一次 */ CURLcode res = curl_easy_perform(m_curl); bool result = true; if(res == CURLE_REMOTE_FILE_NOT_FOUND) { result = false; } else if(res == CURLE_OK) { result = true; } else { LOG_ERROR("Check remote dir error, error code: " << (int)res << ", " << curl_easy_strerror(res)); result = false; } // LOG_DEBUG("Check remote dir: {}, res: {}", dir, (int)res); return result; } /* 检查SFTP文件夹是否存在,这里通过列出上一层的文件夹中的内容,然后进行对比判断的 */ bool CurlFtp::checkSftpDirExist(const std::string& dir) { /* 取出上一层文件夹路径 */ #if (__cplusplus >= 201703L) std::string parentDir = std::filesystem::path(dir).parent_path().string(); #else std::string parentDir = QFileInfo(QString::fromStdString(dir)).path().toStdString(); #endif /* (__cplusplus >= 201703L) */ // LOG_DEBUG("Parent dir: {}", parentDir); std::vector vecDir; bool ret = getDirList(parentDir, vecDir); if(!ret) { LOG_ERROR("Failed to check sftp dir: " << dir); return false; } /* 取出本层文件夹名称 */ #if (__cplusplus >= 201703L) std::string dirName = std::filesystem::path(dir).filename().string(); #else std::string dirName = QFileInfo(QString::fromStdString(dir)).fileName().toStdString(); #endif /* (__cplusplus >= 201703L) */ // LOG_DEBUG("Dir name: {}", dirName); /* 判断是否存在 */ bool result = false; for(const std::string& str : vecDir) { if(str == dirName) { result = true; break; } } return result; }