Tuesday, October 11, 2022
HomeWordPress DevelopmentThe way to hold a historical past of file adjustments with C++

The way to hold a historical past of file adjustments with C++


Right here, I’ll present you how one can hold observe of file adjustments in a listing and retailer them in Reduct Storage through the use of its C++ consumer SDK. Yow will discover the complete working instance right here.



Working Reduct Storage

In the event you’re a Linux person, the best option to run the storage engine is Docker. That is an instance of a docker-compose.yml file:

companies:
  reduct-storage:
    picture: reductstorage/engine:v1.0.1
    volumes:
      - ./knowledge:/knowledge
    surroundings:
      RS_LOG_LEVEL: DEBUG
    ports:
      - 8383:8383
Enter fullscreen mode

Exit fullscreen mode

You can also obtain binaries and run them:

RS_DATA_PATH=./knowledge reduct-storage
Enter fullscreen mode

Exit fullscreen mode

If every part is okay, it is best to see Internet Console on http://127.0.0.1:8383.



Putting in Reduct Storage SDK for C++

At present, you possibly can solely construct and set up the library manually. Observe this instruction.



File Watcher in C++

The SDK offers cmake discover script, so you possibly can simply combine it in your CMake mission. That is the instance of your CMakeLists.txt:

cmake_minimum_required(VERSION 3.23)
mission(file_watcher_example)

set(CMAKE_CXX_STANDARD 20)

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

add_executable(file_watcher primary.cc)
target_link_libraries(file_watcher 
  ${REDUCT_CPP_LIBRARIES} ${ZLIB_LIBRARIES} 
  OpenSSL::SSL OpenSSL::Crypto)
Enter fullscreen mode

Exit fullscreen mode

Now we’re prepared to put in writing C++ code. Our primary.cc file:

#embody <reduct/consumer.h>

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

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

namespace fs = std::filesystem;

int primary() {
  utilizing ReductClient = reduct::IClient;
  utilizing ReductBucket = reduct::IBucket;

  auto consumer = ReductClient::Construct(kReductStorageUrl);

  auto [bucket, err] = consumer->GetOrCreateBucket(
      "watched_files", ReductBucket::Settings{
                           .quota_type = ReductBucket::QuotaType::kFifo,
                           .quota_size = 100'000'000,  // 100Mb
                       });
  if (err) {
    std::cerr << "Didn't 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;
      // examine solely information
      if (!fs::is_regular_file(file)) {
        proceed;
      }

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

      if (file_timestamp_map.incorporates(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) {
        proceed;
      }

      std::string alias = filename;
      std::regex_replace(
          alias.start(), filename.start(), filename.finish(), std::regex("."),
          "_");  // we use filename as an entyr identify. It will probably't include dots.
      std::cout << "`" << filename << "` is modified. 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 dimension) {
              std::string buffer;
              buffer.resize(dimension);
              changed_file.learn(buffer.knowledge(), dimension);
              std::cout << "." << std::flush;
              return std::pair{offest + dimension <= 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;
}

Enter fullscreen mode

Exit fullscreen mode

Okay, it has fairly many traces however don’t be concerned this can be a easy program. Let us take a look at the code intimately.



Making a Bucket

To start out writing to the database, we should create a bucket:

  auto consumer = ReductClient::Construct(kReductStorageUrl);
  auto [bucket, err] = consumer->GetOrCreateBucket(
      "watched_files", ReductBucket::Settings{
                           .quota_type = ReductBucket::QuotaType::kFifo,
                           .quota_size = 100'000'000,  // 100Mb
                       });
  if (err) {
    std::cerr << "Didn't create bucket" << err << std::endl;
    return -1;
  }
Enter fullscreen mode

Exit fullscreen mode

Right here we construct a consumer which ought to use a storage engine with the kReductStorageUrl URL. Then we create a bucket with the watched_files identify or get an current one. Concentrate, we offer some settings as nicely to restrict it dimension with 100Mb, in order that the storage engine begins eradicating outdated knowledge once we attain this quota.
The SDK would not throw any exceptions. Every technique returns reduct::Error or reduct::End result<T>, so you possibly can simply examine the end in your code and print error messages.



Watching Recordsdata

We implement the file watcher in a simple method:

  std::map<std::string, fs::file_time_type> file_timestamp_map;
  for (;;) {
    for (auto& file : fs::directory_iterator(kWatchedPath)) {
      bool is_changed = false;
      // examine solely information
      if (!fs::is_regular_file(file)) {
        proceed;
      }

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

      if (file_timestamp_map.incorporates(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) {
        proceed;
      }

      // Storing a modified file...

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

}

Enter fullscreen mode

Exit fullscreen mode

We journey by way of a given listing fs::directory_iterator(kWatchedPath) and hold the final modification time of every file within the file_timestamp_map map. Whether it is new (wasn’t within the map) or it’s modified (timestamp is totally different), we set the is_changed flag to begin storing the modified file.

Do not forget to sleep some time on the finish of every cycle to keep away from overloading a CPU.



Storing Recordsdata

A historical past of a file is represented as an entry in Reduct Storage. As a result of an entry identify cannot have “.” we must always exchange them in our file names:

  std::string alias = filename;
      std::regex_replace(
          alias.start(), filename.start(), filename.finish(), std::regex("."),
          "_");  // we use filename as an entyr identify. It will probably't include dots.
      std::cout << "`" << filename << "` is modified. Storing as `" << alias
                << "` ";
Enter fullscreen mode

Exit fullscreen mode

Then we open the modified file for studying:

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

Exit fullscreen mode

And write it chunkwise to the storage engine:

      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 dimension) {
              std::string buffer;
              buffer.resize(dimension);
              changed_file.learn(buffer.knowledge(), dimension);
              std::cout << "." << std::flush;
              return std::pair{offest + dimension <= file_size, buffer};
            });
          });
Enter fullscreen mode

Exit fullscreen mode

As you possibly can see, it is fairly verbose, however we ship information with little chunks, and we are able to ship terabytes with none worries about reminiscence! In the event you put an enormous file into your watched listing, you possibly can see how briskly Reduct Storage is.



Getting Knowledge

You may get the information through the use of Bucket::Question technique. You can also use Python or JavaScript Shopper SDKs, and even wget:

wget http://127.0.0.1/api/v1/b/watched_files/<File-Title>
Enter fullscreen mode

Exit fullscreen mode

I hope it was useful! Thanks!

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments