C++檔案系統操作6 - 跨平臺實現檔案和資料夾的複製

陌尘(MoChen)發表於2024-07-26
  • 1. 關鍵詞
  • 2. fileutil.h
  • 3. fileutil.cpp
  • 4. filesystem_win.h
  • 5. filesystem_win.cpp
  • 6. filesystem_unix.cpp
  • 7. 原始碼地址

1. 關鍵詞

C++ 檔案系統操作 複製檔案 複製資料夾 跨平臺

2. fileutil.h


#pragma once

#include <string>
#include <cstdio>
#include <cstdint>
#include "filetype.h"
#include "filepath.h"

namespace cutl
{

    /**
     * @brief The file guard class to manage the FILE pointer automatically.
     * file_guard object can close the FILE pointer automatically when his scope is exit.
     */
    class file_guard
    {
    public:
        /**
         * @brief Construct a new file guard object
         *
         * @param file the pointer of the FILE object
         */
        explicit file_guard(FILE *file);

        /**
         * @brief Destroy the file guard object
         *
         */
        ~file_guard();

        /**
         * @brief Get the FILE pointer.
         *
         * @return FILE*
         */
        FILE *getfd() const;

    private:
        FILE *file_;
    };

    /**
     * @brief Copy a file or symbolic link(shortcut on windows).
     *
     * @param srcpath the filepath of the source file or symbolic link to be copied
     * @param dstpath the filepath of the destination file or symbolic link to be copied to
     * @param attributes whether to copy the file attributes, default is false.
     * If true, means copy the file attributes, like the 'cp -p' command.
     * @return true if the file or symbolic link is copied successfully, false otherwise.
     * @note If the destination file or directory already exists, it will be overwritten.
     * @return false
     */
    bool copyfile(const filepath &srcpath, const filepath &dstpath, bool attributes = false);

    // copy directory recursively
    /**
     * @brief Copy a directory recursively.
     *
     * @param srcdir the filepath of the source directory to be copied
     * @param dstdir the filepath of the destination directory to be copied to
     * @return true if the directory is copied successfully, false otherwise.
     * @note If the destination directory already exists, it will be overwritten.
     */
    bool copydir(const filepath &srcdir, const filepath &dstdir);

} // namespace cutl

3. fileutil.cpp

#include <cstdio>
#include <map>
#include <iostream>
#include <cstring>
#include <sys/stat.h>
#include "fileutil.h"
#include "inner/logger.h"
#include "inner/filesystem.h"
#include "strutil.h"

namespace cutl
{
    file_guard::file_guard(FILE *file)
        : file_(file)
    {
    }

    file_guard::~file_guard()
    {
        if (file_)
        {
            // CUTL_DEBUG("close file");
            int ret = fclose(file_);
            if (ret != 0)
            {
                CUTL_ERROR("fail to close file, ret" + std::to_string(ret));
            }
            file_ = nullptr;
        }
        // ROBOLOG_DCHECK(file_ == nullptr);
    }

    FILE *file_guard::getfd() const
    {
        return file_;
    }

    bool copyfile(const filepath &srcpath, const filepath &dstpath, bool attributes)
    {
        // CUTL_INFO("file type: " + std::to_string(srcpath.type()) + ", " + filetype_flag(srcpath.type()) + ", " + srcpath.str() + ", dstpath:" + dstpath.str());

        // copy file content
        if (srcpath.isfile())
        {
            if (dstpath.exists())
            {
                // remove if exists
                removefile(dstpath);
            }
            file_guard frd(fopen(srcpath.str().c_str(), "rb"));
            if (frd.getfd() == nullptr)
            {
                CUTL_ERROR("open file failed, " + srcpath.str());
                return false;
            }
            file_guard fwt(fopen(dstpath.str().c_str(), "wb"));
            if (fwt.getfd() == nullptr)
            {
                CUTL_ERROR("open file failed, " + dstpath.str());
                return false;
            }

            static constexpr size_t buf_size = 8 * 1024;
            uint8_t buffer[buf_size] = {0};
            size_t read_len = 0;
            size_t write_len = 0;
            while ((read_len = fread(buffer, 1, buf_size, frd.getfd())) > 0)
            {
                write_len = fwrite(buffer, 1, read_len, fwt.getfd());
                if (write_len != read_len)
                {
                    CUTL_ERROR("write file failed, only write " + std::to_string(write_len) + ", read_len:" + std::to_string(read_len));
                    return false;
                }
            }
            // flush file to disk
            int ret = fflush(fwt.getfd());
            if (0 != ret)
            {
                CUTL_ERROR("fail to flush file:" + dstpath.str());
                return false;
            }
            if (!file_sync(fwt.getfd()))
            {
                CUTL_ERROR("file_sync failed for " + dstpath.str());
                return false;
            }
        }
        else if (srcpath.issymlink())
        {
            if (dstpath.exists())
            {
                // remove if exists
                file_removelink(dstpath.str());
            }
            auto link_path = file_readlink(srcpath.str());
            if (link_path.empty())
            {
                CUTL_ERROR("readlink failed for " + srcpath.str());
                return false;
            }
            if (!file_createlink(link_path, dstpath.str()))
            {
                CUTL_ERROR("createlink failed for " + dstpath.str());
                return false;
            }
        }
        else
        {
            CUTL_ERROR("not a file or symlink, cannot copy: [" + filetype_flag(srcpath.type()) + "]" + srcpath.str());
            return false;
        }

        // copy file attributes
        if (attributes && srcpath.isfile())
        {
            return copy_attributes(srcpath.str(), dstpath.str());
        }

        return true;
    }

    // https://www.cnblogs.com/harrypotterjackson/p/12113382.html
    bool copydir(const filepath &srcdir, const filepath &dstdir)
    {
        if (!srcdir.isdir())
        {
            CUTL_ERROR("srcdir is not a directory: " + srcdir.str());
            return false;
        }

        if (!dstdir.exists() && !createdir(dstdir, true))
        {
            CUTL_ERROR("createdir failed for " + dstdir.str());
            return false;
        }

        auto filelist = list_files(srcdir, filetype::all, true);
        for (size_t i = 0; i < filelist.size(); i++)
        {
            auto file = filelist[i];
            auto src_file = file.filepath;
            auto reletive_path = src_file.substr(srcdir.str().length() + 1);
            auto dstpath = dstdir.join(reletive_path);
            auto srcpath = cutl::path(src_file);
            if (file.type == filetype::file || file.type == filetype::symlink)
            {
                if (!copyfile(srcpath, dstpath, true))
                {
                    return false;
                }
            }
            else if (file.type == filetype::directory)
            {
                if (!createdir(dstpath, true))
                {
                    return false;
                }

                if (!copy_attributes(src_file, dstpath.str(), true))
                {
                    return false;
                }
            }
            else
            {
                CUTL_WARN("the file cannot be copy: [" + filetype_flag(srcpath.type()) + "]" + srcpath.str());
                continue;
            }
        }

        return true;
    }

4. filesystem_win.h


#include <vector>
#include <string>

#pragma once

namespace cutl
{
    // 複製檔案/資料夾的屬性,isdir引數只在windows下生效,Unix-like系統下不起作用
    bool copy_attributes(const std::string &srcpath, const std::string &dstpath, bool isdir = false);
} // namespace cutl

5. filesystem_win.cpp

#if defined(_WIN32) || defined(__WIN32__)

#include <io.h>
#include <direct.h>
#include <Windows.h>
#include <stdlib.h>
#include "strutil.h"
#include "filesystem.h"
#include "logger.h"

namespace cutl
{
    bool copy_attributes(const std::string &srcpath, const std::string &dstpath, bool isdir)
    {
        // 獲取 修改訪問、修改時間
        FILETIME t_create, t_access, t_write;
        HANDLE h_src = NULL;
        if (isdir)
        {
            // 資料夾
            h_src = CreateFile(
                srcpath.c_str(),
                GENERIC_READ,
                FILE_SHARE_READ | FILE_SHARE_DELETE,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS,
                NULL);
        }
        else
        {
            h_src = CreateFileA(
                srcpath.c_str(),
                GENERIC_READ,
                0,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
                NULL);
        }

        if (h_src == INVALID_HANDLE_VALUE)
        {
            CUTL_ERROR("Failed to open file " + srcpath + ", error code: " + std::to_string(GetLastError()));
            CloseHandle(h_src);
            return false;
        }
        if (!GetFileTime(h_src, &t_create, &t_access, &t_write))
        {
            CUTL_ERROR("Failed to get file times for " + srcpath + ", error code: " + std::to_string(GetLastError()));
            CloseHandle(h_src);
            return false;
        }
        CloseHandle(h_src);

        // 設定 修改訪問、修改時間
        HANDLE h_dst = NULL;
        if (isdir)
        {
            h_dst = CreateFile(
                dstpath.c_str(),
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_DELETE,
                NULL,
                OPEN_EXISTING,
                FILE_FLAG_BACKUP_SEMANTICS,
                NULL);
        }
        else
        {
            h_dst = ::CreateFileA(
                dstpath.c_str(),
                // GENERIC_WRITE,
                // GENERIC_READ | GENERIC_WRITE,
                GENERIC_ALL,
                0,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL);
        }

        if (h_dst == INVALID_HANDLE_VALUE)
        {
            CUTL_ERROR("Failed to open file " + dstpath + ", error code: " + std::to_string(GetLastError()));
            CloseHandle(h_dst);
            return false;
        }
        if (!SetFileTime(h_dst, &t_create, &t_access, &t_write))
        {
            CUTL_ERROR("Failed to set file times for " + dstpath + ", error code: " + std::to_string(GetLastError()));
            CloseHandle(h_dst);
            return false;
        }
        CloseHandle(h_dst);

        // 修改檔案屬性
        // 注意:檔案訪問許可權的屬性設定要放在檔案時間的修改後面,因為只讀許可權的檔案不允許修改時間
        DWORD attributes = GetFileAttributesA(srcpath.c_str());
        if (attributes == INVALID_FILE_ATTRIBUTES)
        {
            CUTL_ERROR("Failed to get file attributes for " + srcpath + ", error code: " + std::to_string(GetLastError()));
            return false;
        }
        if (!SetFileAttributesA(dstpath.c_str(), attributes))
        {
            CUTL_ERROR("Failed to set file attributes for " + dstpath + ", error code: " + std::to_string(GetLastError()));
            return false;
        }
        return true;
    }
} // namespace cutl

#endif // defined(_WIN32) || defined(__WIN32__)

6. filesystem_unix.cpp

#if defined(_WIN32) || defined(__WIN32__)
// do nothing
#else

#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stack>
#include <cstring>
#include <utime.h>
#include <stdlib.h>
#include <sys/time.h>
#include "filesystem.h"
#include "inner/logger.h"

namespace cutl
{
    bool copy_attributes(const std::string &srcpath, const std::string &dstpath, bool isdir)
    {
        struct stat attr_of_src;
        int ret = lstat(srcpath.c_str(), &attr_of_src);
        if (ret != 0)
        {
            CUTL_ERROR("lstat error. srcpath:" + srcpath + ", error:" + strerror(errno));
            return false;
        }

        // 修改檔案屬性
        ret = chmod(dstpath.c_str(), attr_of_src.st_mode);
        if (ret != 0)
        {
            CUTL_ERROR("chmod error. dstpath:" + dstpath + ", error:" + strerror(errno));
            return false;
        }
        // 修改檔案使用者組
        ret = chown(dstpath.c_str(), attr_of_src.st_uid, attr_of_src.st_gid);
        if (ret != 0)
        {
            CUTL_ERROR("chown error. dstpath:" + dstpath + ", error:" + strerror(errno));
            return false;
        }

        // 修改檔案訪問、修改時間
        if (S_ISLNK(attr_of_src.st_mode))
        {
            // TODO: 編譯還有問題,需要確定編譯宏
            // struct timeval time_buf[2];
            // time_buf[0].tv_sec = attr_of_src.st_atim.tv_sec;
            // time_buf[0].tv_usec = attr_of_src.st_atim.tv_nsec / 1000;
            // time_buf[1].tv_sec = attr_of_src.st_mtim.tv_sec;
            // time_buf[1].tv_usec = attr_of_src.st_mtim.tv_nsec / 1000;
            // ret = lutimes(dstpath.c_str(), time_buf);
            // if (ret != 0)
            // {
            //     CUTL_ERROR("lutimes error. dstpath:" + dstpath + ", error:" + strerror(errno));
            //     return false;
            // }
        }
        else
        {
            struct utimbuf tbuf;
            tbuf.actime = attr_of_src.st_atime;
            tbuf.modtime = attr_of_src.st_mtime;
            ret = utime(dstpath.c_str(), &tbuf);
            if (ret != 0)
            {
                CUTL_ERROR("utime error. dstpath:" + dstpath + ", error:" + strerror(errno));
                return false;
            }
        }

        return true;
    }
} // namespace cutl

#endif // defined(_WIN32) || defined(__WIN32__)

7. 原始碼地址

更多詳細程式碼,請檢視本人寫的C++ 通用工具庫: common_util, 本專案已開源,程式碼簡潔,且有詳細的文件和Demo。


【SunLogging】
C++檔案系統操作6 - 跨平臺實現檔案和資料夾的複製
掃碼二維碼,關注微信公眾號,閱讀更多精彩內容

相關文章