Browse Source

V0.2.14
1、完成了CurlFtp下载文件
2、完成了创建FTP文件夹,也可以递归创建
3、添加了fmtlog不带换行符的日志,可以刷新在同一行

Apple 6 months ago
parent
commit
7f12d4118b
3 changed files with 293 additions and 10 deletions
  1. 257 8
      common/CurlFtp/CurlFtp.cpp
  2. 10 2
      common/CurlFtp/CurlFtp.h
  3. 26 0
      common/FmtLog/fmtlog.h

+ 257 - 8
common/CurlFtp/CurlFtp.cpp

@@ -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;
+}
+
 
 
 

+ 10 - 2
common/CurlFtp/CurlFtp.h

@@ -42,17 +42,25 @@ public:
     bool getDirList(std::string dir, std::vector<std::string>& dirList);
     /* 判断文件夹是否存在 */
     bool isDirExist(const std::string& dir);
+    /* 创建FTP文件夹 */
+    bool createDirectory(const std::string& ftpDir);
+    /* 创建FTP文件夹,递归创建 */
+    bool createDirectories(const std::string& ftpDir);
 
     /* 下载文件 */
     bool downloadFile(const std::string& remoteFile, const std::string& localFile);
+    /* 上传文件 */
+    bool uploadFile(const std::string& localFile, const std::string& remoteFile);
 
 private:
     /* 列出所有内容 */
     bool listAll(CURL* curl, std::string dir, std::vector<CF_FileInfo>& fileInfoList);
-    /* 检查文件夹路径是否合规,不合规就修改 */
+    /* 检查FTP文件夹路径是否合规,不合规就修改 */
     std::string checkDirPath(const std::string& dir);
-    /* 检查文件路径是否合规 */
+    /* 检查FTP文件路径是否合规 */
     std::string checkFilePath(const std::string& file);
+    /* 检查本地文件夹是否存在,不存在则创建 */
+    bool checkLocalDir(const std::string& localDir);
 
 private:
     std::mutex m_mutexCurl;                 /* curl互斥锁 */

+ 26 - 0
common/FmtLog/fmtlog.h

@@ -67,6 +67,32 @@ const std::regex _reg_file(R"(.*/([\S]*[\.][\S]*)$)");
         fmt::print(fg(fmt::color::red), "[{}][{}][{}:{}] {}\n", _log_time, "ERROR", _log_file, __LINE__, _log_str); \
     }while(0)
 
+/******************** 不输出换行符 *********************/
+/* Debug输出 */
+#define FMTLOG_DEBUG_NON(...) \
+    do{ \
+        auto _now = std::chrono::system_clock::now(); \
+        std::time_t _now_c = std::chrono::system_clock::to_time_t(_now); \
+        std::string _log_time = fmt::format("{:%Y-%m-%d %H:%M:%S}", fmt::localtime(_now_c)); \
+        std::string _log_file_src = __FILE__; \
+        auto _log_file = std::regex_replace(_log_file_src, _reg_file, "$1"); \
+        std::string _log_str = fmt::format(__VA_ARGS__); \
+        fmt::print(fg(fmt::color::blue), "[{}][{}][{}:{}] {}", _log_time, "DEBUG", _log_file , __LINE__ , _log_str); \
+    }while(0)
+
+/* 正常输出 */
+#define FMTLOG_INFO_NON(...) \
+    do{ \
+        auto _now = std::chrono::system_clock::now(); \
+        std::time_t _now_c = std::chrono::system_clock::to_time_t(_now); \
+        std::string _log_time = fmt::format("{:%Y-%m-%d %H:%M:%S}", fmt::localtime(_now_c)); \
+        std::string _log_file_src = __FILE__; \
+        auto _log_file = std::regex_replace(_log_file_src, _reg_file, "$1"); \
+        std::string _log_str = fmt::format(__VA_ARGS__); \
+        fmt::print(fg(fmt::color::green), "[{}][{}][{}:{}] {}", _log_time, "INFO", _log_file, __LINE__, _log_str); \
+    }while(0)
+
+
 // void hello()
 // {
 //     auto now = std::chrono::system_clock::now();