C++로 파일 변경 내역을 유지하는 방법

여기서는 Reduct Storage 을 사용하여 디렉토리에서 파일 변경 사항을 추적하고 C++ client SDK 에 저장하는 방법을 보여드리겠습니다. 전체 작업 예제here를 찾을 수 있습니다.

축소 스토리지 실행



Linux 사용자인 경우 저장소 엔진을 실행하는 가장 쉬운 방법은 Docker입니다. 다음은 docker-compose.yml 파일의 예입니다.

services:
  reduct-storage:
    image: reductstorage/engine:v1.0.1
    volumes:
      - ./data:/data
    environment:
      RS_LOG_LEVEL: DEBUG
    ports:
      - 8383:8383


다운로드binaries하고 실행할 수도 있습니다.

RS_DATA_PATH=./data reduct-storage


모든 것이 정상이면 http://127.0.0.1:8383에 웹 콘솔이 표시되어야 합니다.

C++용 Reduction Storage SDK 설치



현재 라이브러리를 수동으로만 빌드하고 설치할 수 있습니다. 팔로우this instruction .

C++의 파일 감시자



SDK는 cmake find 스크립트를 제공하므로 CMake 프로젝트에 쉽게 통합할 수 있습니다. 다음은 CMakeLists.txt의 예입니다.

cmake_minimum_required(VERSION 3.23)
project(file_watcher_example)

set(CMAKE_CXX_STANDARD 20)

find_package(ReductCpp 1.0.1)
find_package(ZLIB)
find_package(OpenSSL)

add_executable(file_watcher main.cc)
target_link_libraries(file_watcher 
  ${REDUCT_CPP_LIBRARIES} ${ZLIB_LIBRARIES} 
  OpenSSL::SSL OpenSSL::Crypto)


이제 C++ 코드를 작성할 준비가 되었습니다. 당사main.cc 파일:

#include <reduct/client.h>

#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <regex>
#include <thread>

constexpr std::string_view kReductStorageUrl = "http://127.0.0.1:8383";
constexpr std::string_view kWatchedPath = "./";

namespace fs = std::filesystem;

int main() {
  using ReductClient = reduct::IClient;
  using ReductBucket = reduct::IBucket;

  auto client = ReductClient::Build(kReductStorageUrl);

  auto [bucket, err] = client->GetOrCreateBucket(
      "watched_files", ReductBucket::Settings{
                           .quota_type = ReductBucket::QuotaType::kFifo,
                           .quota_size = 100'000'000,  // 100Mb
                       });
  if (err) {
    std::cerr << "Failed to create bucket" << err << std::endl;
    return -1;
  }

  std::cout << "Create bucket" << std::endl;

  std::map<std::string, fs::file_time_type> file_timestamp_map;
  for (;;) {
    for (auto& file : fs::directory_iterator(kWatchedPath)) {
      bool is_changed = false;
      // check only files
      if (!fs::is_regular_file(file)) {
        continue;
      }

      const auto filename = file.path().filename().string();
      auto ts = fs::last_write_time(file);

      if (file_timestamp_map.contains(filename)) {
        auto& last_ts = file_timestamp_map[filename];
        if (ts != last_ts) {
          is_changed = true;
        }
        last_ts = ts;
      } else {
        file_timestamp_map[filename] = ts;
        is_changed = true;
      }

      if (!is_changed) {
        continue;
      }

      std::string alias = filename;
      std::regex_replace(
          alias.begin(), filename.begin(), filename.end(), std::regex("\\."),
          "_");  // we use filename as an entyr name. It can't contain dots.
      std::cout << "`" << filename << "` is changed. Storing as `" << alias
                << "` ";

      std::ifstream changed_file(file.path());
      if (!changed_file) {
        std::cerr << "Failed open file";
        return -1;
      }

      auto file_size = fs::file_size(file);

      auto write_err = bucket->Write(
          alias, std::chrono::file_clock::to_sys(ts),
          [file_size, &changed_file](ReductBucket::WritableRecord* rec) {
            rec->Write(file_size, [&](size_t offest, size_t size) {
              std::string buffer;
              buffer.resize(size);
              changed_file.read(buffer.data(), size);
              std::cout << "." << std::flush;
              return std::pair{offest + size <= file_size, buffer};
            });
          });

      if (write_err) {
        std::cout << " Err:" << write_err << std::endl;
      } else {
        std::cout << " OK (" << file_size / 1024 << " kB)" << std::endl;
      }
    }

    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }
  return 0;
}



좋아요, 꽤 많은 줄이 있지만 걱정하지 마세요. 이것은 간단한 프로그램입니다. 코드를 자세히 살펴보겠습니다.

버킷 생성



데이터베이스에 쓰기를 시작하려면 버킷을 생성해야 합니다.

  auto client = ReductClient::Build(kReductStorageUrl);
  auto [bucket, err] = client->GetOrCreateBucket(
      "watched_files", ReductBucket::Settings{
                           .quota_type = ReductBucket::QuotaType::kFifo,
                           .quota_size = 100'000'000,  // 100Mb
                       });
  if (err) {
    std::cerr << "Failed to create bucket" << err << std::endl;
    return -1;
  }


여기에서 kReductStorageUrl URL이 있는 스토리지 엔진을 사용해야 하는 클라이언트를 빌드합니다. 그런 다음 이름이 watched_files인 버킷을 생성하거나 기존 버킷을 가져옵니다. 이 할당량에 도달하면 스토리지 엔진이 오래된 데이터를 제거하기 시작하도록 100Mb로 크기를 제한하는 몇 가지 설정도 제공합니다.
SDK는 예외를 발생시키지 않습니다. 각 메서드는 reduct::Error 또는 reduct::Result<T> 를 반환하므로 코드에서 결과를 쉽게 확인하고 오류 메시지를 인쇄할 수 있습니다.

파일 보기



간단한 방법으로 파일 감시자를 구현합니다.

  std::map<std::string, fs::file_time_type> file_timestamp_map;
  for (;;) {
    for (auto& file : fs::directory_iterator(kWatchedPath)) {
      bool is_changed = false;
      // check only files
      if (!fs::is_regular_file(file)) {
        continue;
      }

      const auto filename = file.path().filename().string();
      auto ts = fs::last_write_time(file);

      if (file_timestamp_map.contains(filename)) {
        auto& last_ts = file_timestamp_map[filename];
        if (ts != last_ts) {
          is_changed = true;
        }
        last_ts = ts;
      } else {
        file_timestamp_map[filename] = ts;
        is_changed = true;
      }

      if (!is_changed) {
        continue;
      }

      // Storing a changed file...

      std::this_thread::sleep_for(
std::chrono::milliseconds(100));

}



주어진 디렉토리fs::directory_iterator(kWatchedPath)를 통해 이동하고 각 파일의 마지막 수정 시간을 file_timestamp_map 맵에 보관합니다. 새 파일(맵에 없음)이거나 변경된 경우(타임스탬프가 다름) 변경된 파일 저장을 시작하도록 is_changed 플래그를 설정합니다.

CPU 과부하를 피하기 위해 각 주기가 끝날 때 잠시 잠을 자는 것을 잊지 마십시오.

파일 저장



파일의 히스토리는 Reduct Storage에서 an entry으로 표현됩니다. 항목 이름에 "."을 사용할 수 없기 때문입니다. 파일 이름에서 다음과 같이 바꿔야 합니다.

  std::string alias = filename;
      std::regex_replace(
          alias.begin(), filename.begin(), filename.end(), std::regex("\\."),
          "_");  // we use filename as an entyr name. It can't contain dots.
      std::cout << "`" << filename << "` is changed. Storing as `" << alias
                << "` ";


그런 다음 읽기 위해 변경된 파일을 엽니다.

     std::ifstream changed_file(file.path());
      if (!changed_file) {
        std::cerr << "Failed open file";
        return -1;
      }


그리고 스토리지 엔진에 청크 단위로 작성합니다.

      auto file_size = fs::file_size(file);
      auto write_err = bucket->Write(
          alias, std::chrono::file_clock::to_sys(ts),
          [file_size, &changed_file](ReductBucket::WritableRecord* rec) {
            rec->Write(file_size, [&](size_t offest, size_t size) {
              std::string buffer;
              buffer.resize(size);
              changed_file.read(buffer.data(), size);
              std::cout << "." << std::flush;
              return std::pair{offest + size <= file_size, buffer};
            });
          });


보시다시피 매우 장황하지만 작은 덩어리로 파일을 보내고 메모리에 대한 걱정 없이 테라바이트를 보낼 수 있습니다! 거대한 파일을 여러분의 감시 디렉토리에 넣으면 Reduct Storage가 얼마나 빠른지 알 수 있습니다.

데이터 가져오기



Bucket::Query 메서드를 사용하여 데이터를 가져올 수 있습니다. 또한 Python 또는 JavaScript 클라이언트 SDK를 사용하거나 wget도 사용할 수 있습니다.

wget http://127.0.0.1/api/v1/b/watched_files/<File-Name>


도움이 되었기를 바랍니다. 감사!

좋은 웹페이지 즐겨찾기