| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950 | 
#include "CurlFtp.h"#include <regex>#include <iosfwd>#include <filesystem>#include <fstream>#include "fmtlog.h"#if defined(_WIN32)#endif /* _WIN32 *//* ================================================================================== * *********************************** 全局变量 ************************************* * ================================================================================== *//* 是否有初始化实例,主要用于静态函数调用curl_global_cleanup()函数前判断,有实例就不调用 */static bool hasInstace = false;/* ================================================================================== * *********************************** 全局函数 ************************************* * ================================================================================== *//** * @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);    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<std::string>* fileList = static_cast<std::vector<std::string>*>(userp);    std::string line(static_cast<char*>(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 writeDataCallBack(void* contents, size_t size, size_t nmemb, std::ostream* pFile){    pFile->write(reinterpret_cast<char*>(contents), size * nmemb);    return size * nmemb;}/** * @brief 读取文件回调函数 *  * @param contents 下载到的数据内容 * @param size 数据大小 * @param nmemb 数据单位 * @param pFile 文件指针 * @return size_t 写入的大小 */static size_t readDataCallBack(void* contents, size_t size, size_t nmemb, std::istream* pFile){        pFile->read(reinterpret_cast<char*>(contents), size * nmemb);    /* 获取读取到的字节数,可能读取到文件末尾,所以不能直接使用传入的字节数 */    size_t readSize = pFile->gcount();    return readSize;    }/** * @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 : {} / {}, {:.2f}%\r", dltotal, dlnow, downloadPercent);    }    /* 正在上传 */    else if(ultotal > 0)    {        double uploadPercent = (double)ulnow / (double)ultotal * 100;        FMTLOG_DEBUG_NON("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 *//** * @brief 解析字符串文件信息,如果FTP服务器规定好个编码,不需要进行转换 *  * @param strSrc 读取到的字符串 * @param fileList 文件信息列表 * @return true  * @return false  */static bool parseFileInfo(const std::string& strSrc, std::vector<CF_FileInfo>& fileInfo){#if defined(_WIN32)    // auto str1 = GBToUTF8(strSrc.c_str());    std::string str2 = strSrc;#else    std::string str2 = strSrc;#endif /* _WIN32 */        // FMTLOG_DEBUG("\n{}", str2);    /* 正则表达式,匹配多个空格 */    std::regex reg(R"(( )+)");    /* 匹配以非空格开头的字符,空格隔开,中间任意字符,空格隔开,非空格组成的结尾     * (文件类型) ... (文件大小,$5) ... (文件名)     */    std::regex reg1(R"(^([^ ]+) (\d*) (\w*) (\w*) (\d*) (.*) ([^ ]+)$)");    // FMTLOG_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(), reg2, -1);    /* 这里取出每一行 */    for(; it2 != std::sregex_token_iterator(); ++it2)    {        /* 去掉多余的空格 */        auto line = std::regex_replace(it2->str(), reg, " ");        // FMTLOG_INFO("{}", line);        CF_FileInfo fi;        /* 取出文件类型 */        std::string strFileType = std::regex_replace(line, reg1, "$1");        if(std::regex_match(strFileType, reg3))        {            fi.type = CF_FileType::DIR;        }else if (std::regex_match(strFileType, reg4))        {            fi.type = CF_FileType::FILE;        }        /* 取出文件大小 */        std::string strFileSize = std::regex_replace(line, reg1, "$5");        fi.size = std::stoull(strFileSize);        /* 取出文件名 */        std::string strFileName = std::regex_replace(line, reg1, "$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_global_cleanup();    hasInstace = false;}/** * @brief 列出FTP文件夹 *  * @param ftpUrl 需要列出文件夹的FTP地址 * @param username  * @param password  * @return std::string  */std::string CurlFtp::listDir(const std::string &ftpUrl, const std::string &username, const std::string &password){    CURL *curl;    CURLcode res;    bool result = false;    std::string retList;    /* 1. 初始化curl,这个函数需要在调用任何curl函数之前 */    curl_global_init(CURL_GLOBAL_DEFAULT);    /* 2. 获取一个curl句柄 */    curl = curl_easy_init();    if(curl)     {        /* 3. 设置curl选项 */        curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());        curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());        curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());        // curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L); // We only want the directory listing        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringCallback);        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &retList);        /* 4. 发送信息,一直等待知道返回 */        res = curl_easy_perform(curl);        // printf("res = %d\n", res);        if(res != CURLE_OK)         {            fprintf(stderr, "getDirList() failed, error code %d, :%s\n",res, curl_easy_strerror(res));        } else         {            // std::cout << "Directory list: \n" << retList << std::endl;        }        /* 5. 清理curl */        curl_easy_cleanup(curl);    }    if(hasInstace == false)    {        curl_global_cleanup();    }    return retList;}/* 列出文件夹中的所有文件 */bool CurlFtp::listFiles(const std::string &ftpUrl, const std::string &username, const std::string &password, std::vector<std::string>& fileList){    CURL *curl;    CURLcode res;    bool result = false;    curl_global_init(CURL_GLOBAL_DEFAULT);    curl = curl_easy_init();    if(curl)    {        curl_easy_setopt(curl, CURLOPT_URL, (ftpUrl).c_str());        curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());        curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());        curl_easy_setopt(curl, CURLOPT_DIRLISTONLY, 1L);        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeStringListCallback);        curl_easy_setopt(curl, CURLOPT_WRITEDATA, &fileList);        res = curl_easy_perform(curl);        if(res != CURLE_OK)         {            fprintf(stderr, "Failed to get file list, error code :%d ,%s\n", res, curl_easy_strerror(res));        }        else        {            // for(const std::string& filename : fileList)            // {            //     printf("%s\n", filename.c_str());            // }            result = true;        }        curl_easy_cleanup(curl);    }    if(hasInstace == false)    {        curl_global_cleanup();    }    return result;}/* 创建文件夹 */bool CurlFtp::createDir(const std::string &ftpUrl, const std::string &username, const std::string &password, const std::string &dirName){    CURL *curl;    CURLcode res;    bool result = false;    curl_global_init(CURL_GLOBAL_DEFAULT);    curl = curl_easy_init();    if(curl)    {        curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());        curl_easy_setopt(curl, CURLOPT_USERNAME, username.c_str());        curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str());        // Create a list of FTP commands to be executed on the server        struct curl_slist *headerlist = NULL;        std::string mkdir = "MKD " + dirName;        headerlist = curl_slist_append(headerlist, mkdir.c_str());        // Set the list of FTP commands to be executed on the server before the transfer        curl_easy_setopt(curl, CURLOPT_QUOTE, headerlist);        // Tell libcurl to not include the body in the output        curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);        res = curl_easy_perform(curl);        if(res != CURLE_OK)         {            fprintf(stderr, "createDir() failed, error code :%d ,%s\n", res, curl_easy_strerror(res));            result = false;        }else        {            result = true;        }        curl_easy_cleanup(curl);        curl_slist_free_all(headerlist);    }    if(hasInstace == false)    {        curl_global_cleanup();    }    return result;}/* 设置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);    FMTLOG_INFO("Set ftpUrl = {}", m_ftpUrl);    return true;}/* 设置用户名和密码 */bool CurlFtp::setFtpUsernameAndPassword(const std::string& username, const std::string& password){    m_username = username;    m_password = password;    return true;}/* 列出文件列表 */bool CurlFtp::getFileList(std::string dir, std::vector<std::string>& fileList){    if(m_IP.empty())    {        FMTLOG_WARN("IP or port is empty");        return false;    }    /* 检查dir,添加前缀“/” */    auto dirTmp = checkDirPath(dir);    CURL *curl = nullptr;    curl = curl_easy_init();    if(curl == nullptr)    {        FMTLOG_ERROR("curl init failed !");        return false;    }    std::vector<CF_FileInfo> listInfo;    /* 获取文件信息 */    listAll(curl, dirTmp, listInfo);    for(const CF_FileInfo& fi : listInfo)    {        // FMTLOG_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<std::string>& dirList){    if(m_IP.empty())    {        FMTLOG_WARN("IP or port is empty");        return false;    }    /* 检查dir,添加前缀“/” */    auto dirTmp = checkDirPath(dir);    CURL *curl = nullptr;    curl = curl_easy_init();    if(curl == nullptr)    {        FMTLOG_ERROR("curl init failed !");        return false;    }    std::vector<CF_FileInfo> listInfo;    /* 获取文件信息 */    listAll(curl, dirTmp, listInfo);    for(const CF_FileInfo& fi : listInfo)    {        // FMTLOG_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;}/* 判断文件夹是否存在 */bool CurlFtp::isDirExist(const std::string& dir){    if(m_ftpUrl.empty())    {        FMTLOG_ERROR("ftpUrl is empty");        return false;    }    /* 检查传入的文件夹 */    auto dirTmp = checkDirPath(dir);    CURL* curl = nullptr;    curl = curl_easy_init();    if(curl == nullptr)    {        FMTLOG_ERROR("curl init failed !");        return false;    }    std::string ftpUrl = m_ftpUrl + dirTmp;    curl_easy_setopt(curl, CURLOPT_URL, ftpUrl.c_str());    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_NOBODY, 1L);    CURLcode res = curl_easy_perform(curl);    bool result = true;    if(res != CURLE_OK)    {        // 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){    if(m_ftpUrl.empty())    {        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)    {        FMTLOG_ERROR("curl init failed !");        return false;    }    /* 打开文件 */    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){    if(m_ftpUrl.empty())    {        FMTLOG_ERROR("ftpUrl is empty");        return false;    }    /* 检查本地文件是否存在 */    if(!std::filesystem::exists(localFile))    {        FMTLOG_ERROR("Local file is not exist: {}", localFile);        return false;    }    /* 检查FTP文件名是否符合规范 */    std::string remoteFileTmp = checkFilePath(remoteFile);    std::string remoteDirTmp = remoteFileTmp.substr(0, remoteFileTmp.find_last_of("/"));    /* 检查远程FTP上的文件夹是否存在,不存在则创建 */    if(!isDirExist(remoteDirTmp))    {        if(!createDirectories(remoteDirTmp))        {            // FMTLOG_ERROR("Failed to create remote dir: {}", remoteFileTmp);            return false;        }    }    /* 拼接远程文件的url */    std::string ftpUrl = m_ftpUrl + remoteFileTmp;        /* 打开文件 */    std::ifstream ifs;    ifs.open(localFile, std::ios::in | std::ios::binary);    if(!ifs.is_open())    {        FMTLOG_ERROR("Failed to open local file: {}", localFile);        return false;    }    /* 获取文件大小 */    // auto startPos = ifs.tellg();    ifs.seekg(0, std::ios::end);    auto fileSize = ifs.tellg();    /* 恢复指针到文件头 */    ifs.seekg(0, std::ios::beg);    FMTLOG_DEBUG("File size: {}", (long)fileSize);    CURL* curl = nullptr;    curl = curl_easy_init();    if(curl == nullptr)    {        FMTLOG_ERROR("curl init failed !");        ifs.close();        return false;    }    /* 设置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_UPLOAD, 1L);    /* 设置回调函数 */    curl_easy_setopt(curl, CURLOPT_READFUNCTION, readDataCallBack);    curl_easy_setopt(curl, CURLOPT_READDATA, &ifs);    /* 设置上传文件的大小 */    curl_easy_setopt(curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)fileSize);    /* 设置超时时间 */    // 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);    bool result = true;    if(res != CURLE_OK)    {        FMTLOG_ERROR("Upload file failed, error code: {} ,{}", (int)res, curl_easy_strerror(res));        FMTLOG_ERROR("ftpUrl = {}", ftpUrl);        result = false;    }    /* 关闭文件,清理curl */    ifs.close();    curl_easy_cleanup(curl);    /* 打印一个换行符 */    FMTLOG_DEBUG_NON("\n");    return result;}/** * @brief 列出文件列表,这个需要的参数很多,无法设置成静态函数 *  * @param curl CURL句柄 * @param dir 文件夹,相对路径,不带有IP和端口号 * @param fileList 返回值,文件列表 * @return true  * @return false  */bool CurlFtp::listAll(CURL* curl, std::string dir, std::vector<CF_FileInfo>& fileInfoList){    if(m_IP.empty())    {        FMTLOG_ERROR("IP is empty");        return false;    }    if(curl == nullptr)    {        FMTLOG_ERROR("curl is nullptr");        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);    /* 发送请求 */    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);        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))    {        FMTLOG_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(std::filesystem::exists(localDirTmp))    {        return true;    }    /* 创建文件夹 */    if(!std::filesystem::create_directories(localDirTmp))    {        FMTLOG_ERROR("Failed to create local dir: {}", localDirTmp);        return false;    }    return true;}
 |