|
@@ -1,8 +1,9 @@
|
|
|
|
|
|
#include "CurlFtp.h"
|
|
|
-#include <iostream>
|
|
|
#include <regex>
|
|
|
#include <iosfwd>
|
|
|
+#include <filesystem>
|
|
|
+#include <fstream>
|
|
|
|
|
|
#include "fmtlog.h"
|
|
|
|
|
@@ -78,12 +79,57 @@ static int writeStringListCallback(void* buffer, size_t size, size_t nmemb, void
|
|
|
* @param pFile 文件指针
|
|
|
* @return size_t 写入的大小
|
|
|
*/
|
|
|
-static size_t writeDataCallBack(void* contents, size_t size, size_t nmemb, FILE* pFile)
|
|
|
+static size_t writeDataCallBack(void* contents, size_t size, size_t nmemb, std::ostream* pFile)
|
|
|
{
|
|
|
- size_t written = fwrite(contents, size, nmemb, pFile);
|
|
|
- return written;
|
|
|
+ pFile->write(reinterpret_cast<char*>(contents), size * nmemb);
|
|
|
+ // if(pFile->fail())
|
|
|
+ // {
|
|
|
+ // FMTLOG_ERROR("Failed to write data to file");
|
|
|
+ // return 0;
|
|
|
+ // }
|
|
|
+ return size * nmemb;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * @brief 上传和下载进度回调函数,这个函数kennel会被多次调用,即使是没有在下载的时候,因此需要判断传入的数据是否是0
|
|
|
+ * 必须将 CURLOPT_NOPROGRESS 设为 0 才能真正调用该函数。
|
|
|
+ *
|
|
|
+ * @param clientp 通过CURLOPT_XFERINFODATA 设置的指针,libcurl 不会使用它,只会将其从应用程序传递给回调函数。
|
|
|
+ * 可以通过这个指针将下载的进度传递给应用程序
|
|
|
+ * @param dltotal 下载的总字节数,上传的时候这个为0
|
|
|
+ * @param dlnow 已经下载的总字节数
|
|
|
+ * @param ultotal 需要上传的总字节数,下载的时候这个为0
|
|
|
+ * @param ulnow 已经上传的总字节数
|
|
|
+ * @return int
|
|
|
+ */
|
|
|
+static int progress_callback(void *clientp,
|
|
|
+ curl_off_t dltotal,
|
|
|
+ curl_off_t dlnow,
|
|
|
+ curl_off_t ultotal,
|
|
|
+ curl_off_t ulnow)
|
|
|
+{
|
|
|
+ // FMTLOG_DEBUG("dTotal: {}, dNow: {}, uTotal: {}, uNow: {}", dltotal, dlnow, ultotal, ulnow);
|
|
|
+ if(dltotal == 0 && ultotal == 0)
|
|
|
+ {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ /* 正在下载 */
|
|
|
+ if(dltotal > 0)
|
|
|
+ {
|
|
|
+ /* 计算进度,百分比 */
|
|
|
+ double downloadPercent = (double)dlnow / (double)dltotal * 100;
|
|
|
+ /* 使用回车刷新这一行 */
|
|
|
+ FMTLOG_DEBUG_NON("Download Total / now : {} / {}, {:.1f}%\r", dltotal, dlnow, downloadPercent);
|
|
|
+ }
|
|
|
+ /* 正在上传 */
|
|
|
+ else if(ultotal > 0)
|
|
|
+ {
|
|
|
+ double uploadPercent = (double)ulnow / (double)ultotal * 100;
|
|
|
+ FMTLOG_DEBUG_NON("Upload Total / now : {} / {}, {:.1f}%\r", ultotal, ulnow, uploadPercent);
|
|
|
+ }
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
|
|
|
|
|
|
/* 使用Windows API进行编码转换 */
|
|
@@ -342,7 +388,7 @@ bool CurlFtp::setFtpIPAndPort(const std::string& IP, const int port)
|
|
|
m_IP = IP;
|
|
|
m_port = port;
|
|
|
m_ftpUrl = "ftp://" + m_IP + ":" + std::to_string(m_port);
|
|
|
- FMTLOG_INFO("ftpUrl = {}", m_ftpUrl);
|
|
|
+ FMTLOG_INFO("Set ftpUrl = {}", m_ftpUrl);
|
|
|
|
|
|
return true;
|
|
|
}
|
|
@@ -460,13 +506,120 @@ bool CurlFtp::isDirExist(const std::string& dir)
|
|
|
bool result = true;
|
|
|
if(res != CURLE_OK)
|
|
|
{
|
|
|
- FMTLOG_ERROR("Failed to get file list, error code: {} ,{}", (int)res, curl_easy_strerror(res));
|
|
|
+ // FMTLOG_ERROR("Failed to get file list, error code: {} ,{}", (int)res, curl_easy_strerror(res));
|
|
|
result = false;
|
|
|
}
|
|
|
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+/**
|
|
|
+ * @brief 创建FTP文件夹,只能创建一层文件夹
|
|
|
+ *
|
|
|
+ * @param ftpDir 文件夹路径
|
|
|
+ * @return true 文件夹创建成功或者文件夹存在
|
|
|
+ * @return false
|
|
|
+ */
|
|
|
+bool CurlFtp::createDirectory(const std::string& ftpDir)
|
|
|
+{
|
|
|
+ if(m_ftpUrl.empty())
|
|
|
+ {
|
|
|
+ FMTLOG_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)
|
|
|
+ {
|
|
|
+ FMTLOG_ERROR("Create FTP DIR, curl init failed !");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ curl_easy_setopt(curl, CURLOPT_URL, m_ftpUrl.c_str());
|
|
|
+ curl_easy_setopt(curl, CURLOPT_USERNAME, m_username.c_str());
|
|
|
+ curl_easy_setopt(curl, CURLOPT_PASSWORD, m_password.c_str());
|
|
|
+ /* 创建FTP头信息 */
|
|
|
+ struct curl_slist *headerlist = NULL;
|
|
|
+ std::string mkdir = "MKD " + dirTmp;
|
|
|
+ headerlist = curl_slist_append(headerlist, mkdir.c_str());
|
|
|
+ /* 设置FTP命令行选项 */
|
|
|
+ curl_easy_setopt(curl, CURLOPT_QUOTE, headerlist);
|
|
|
+ /* 不包含实体 */
|
|
|
+ curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
|
|
|
+ /* 启用跟随重定向 */
|
|
|
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
+ /* 设置超时时间 */
|
|
|
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
|
|
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
|
|
|
+
|
|
|
+ CURLcode res = curl_easy_perform(curl);
|
|
|
+ bool result = true;
|
|
|
+ if(res != CURLE_OK)
|
|
|
+ {
|
|
|
+ FMTLOG_ERROR("Failed to create FTP Dir, error code: {} ,{}", (int)res, curl_easy_strerror(res));
|
|
|
+ result = false;
|
|
|
+ }else
|
|
|
+ {
|
|
|
+ result = true;
|
|
|
+ }
|
|
|
+ curl_easy_cleanup(curl);
|
|
|
+ curl_slist_free_all(headerlist);
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+/* 创建FTP文件夹,递归创建 */
|
|
|
+bool CurlFtp::createDirectories(const std::string& ftpDir)
|
|
|
+{
|
|
|
+ /* 检查路径格式,并去掉第一个 / */
|
|
|
+ std::string ftpDirTmp = checkDirPath(ftpDir);
|
|
|
+ std::string ftpDir2 = ftpDirTmp.substr(1, ftpDirTmp.size() -1);
|
|
|
+ /* 将文件夹分开,取出每一个文件夹名称 */
|
|
|
+ std::vector<std::string> 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<std::string> 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)
|
|
|
+ {
|
|
|
+ // FMTLOG_DEBUG("Create dir: {}", dir);
|
|
|
+ if(!createDirectory(dir))
|
|
|
+ {
|
|
|
+ FMTLOG_ERROR("Failed to create dir: {}", dir);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
/* 下载文件 */
|
|
|
bool CurlFtp::downloadFile(const std::string& remoteFile, const std::string& localFile)
|
|
|
{
|
|
@@ -475,6 +628,19 @@ bool CurlFtp::downloadFile(const std::string& remoteFile, const std::string& loc
|
|
|
FMTLOG_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))
|
|
|
+ {
|
|
|
+ FMTLOG_ERROR("Failed to create local dir: {}", localDirTmp);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
CURL* curl = nullptr;
|
|
|
curl = curl_easy_init();
|
|
|
if(curl == nullptr)
|
|
@@ -482,13 +648,68 @@ bool CurlFtp::downloadFile(const std::string& remoteFile, const std::string& loc
|
|
|
FMTLOG_ERROR("curl init failed !");
|
|
|
return false;
|
|
|
}
|
|
|
- /* 检查传入的文件是否符合规范 */
|
|
|
- std::string ftpUrl = m_ftpUrl + remoteFile;
|
|
|
+ /* 打开文件 */
|
|
|
+ std::ofstream ofs;
|
|
|
+ ofs.open(localFile, std::ios::out | std::ios::binary | std::ios::trunc);
|
|
|
+
|
|
|
+ /* 设置FTP地址 */
|
|
|
+ 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(curl, CURLOPT_FOLLOWLOCATION, 1L);
|
|
|
+ /* 设置回调函数 */
|
|
|
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeDataCallBack);
|
|
|
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, &ofs);
|
|
|
+ /* 设置超时时间 */
|
|
|
+ // curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L);
|
|
|
+ curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 30L);
|
|
|
+ /* 设置进度回调函数 */
|
|
|
+ curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
|
|
|
+ curl_easy_setopt(curl, CURLOPT_XFERINFODATA, nullptr);
|
|
|
+ /* 启用下载进度回调函数 */
|
|
|
+ curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
|
|
|
+
|
|
|
+ /* 发送请求 */
|
|
|
+ CURLcode res = curl_easy_perform(curl);
|
|
|
+ if(res != CURLE_OK)
|
|
|
+ {
|
|
|
+ FMTLOG_ERROR("Failed to get file list, error code: {} ,{}", (int)res, curl_easy_strerror(res));
|
|
|
+ FMTLOG_ERROR("ftpUrl = {}", ftpUrl);
|
|
|
+ /* 清理下载失败的文件 */
|
|
|
+ ofs.close();
|
|
|
+ std::remove(localFile.c_str());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ /* 关闭文件,清理curl */
|
|
|
+ ofs.close();
|
|
|
+ curl_easy_cleanup(curl);
|
|
|
|
|
|
+ /* 打印一个换行符 */
|
|
|
+ FMTLOG_DEBUG_NON("\n");
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+/**
|
|
|
+ * @brief 上传文件
|
|
|
+ *
|
|
|
+ * @param localFile 本地文件
|
|
|
+ * @param remoteFile 远程文件
|
|
|
+ * @return true
|
|
|
+ * @return false
|
|
|
+ */
|
|
|
+bool CurlFtp::uploadFile(const std::string& localFile, const std::string& remoteFile)
|
|
|
+{
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
|
|
|
/**
|
|
|
* @brief 列出文件列表,这个需要的参数很多,无法设置成静态函数
|
|
@@ -595,6 +816,34 @@ std::string CurlFtp::checkFilePath(const std::string& file)
|
|
|
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(std::filesystem::exists(localDirTmp))
|
|
|
+ {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ /* 创建文件夹 */
|
|
|
+ if(!std::filesystem::create_directories(localDirTmp))
|
|
|
+ {
|
|
|
+ FMTLOG_ERROR("Failed to create local dir: {}", localDirTmp);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
|
|
|
|